Compare commits

..

38 Commits

Author SHA1 Message Date
Kent Dong
4f16d6b70f feat: Support installing higress to a K8s cluster with kernel version < 4.11 (#301) 2023-04-24 15:29:39 +08:00
Tom Kerkhove
8c41dbc376 fix: Bug issue template is broken (#292) 2023-04-12 21:03:43 +08:00
Tom Kerkhove
e98788a75c chore: Introduce issue chooser for better issue reporting (#294) 2023-04-12 21:03:22 +08:00
Tom Kerkhove
3b5850a5ba docs: Move Console details after core features in README (#291) 2023-04-12 21:02:39 +08:00
澄潭
df60dd4307 add intro gif (#283) 2023-04-10 17:12:08 +08:00
澄潭
283432b6eb Fix destination rule issues (#282) 2023-04-10 13:41:43 +08:00
澄潭
a9742bbae1 Update README.md 2023-04-10 11:53:28 +08:00
澄潭
069b636c10 Update extensions & release 1.0.0-rc (#281) 2023-04-09 14:16:10 +08:00
Ffyyt
f5edac0c58 README translation of key-auth plugin (#279) 2023-04-09 14:09:34 +08:00
澄潭
06b09066a3 patch istio to support multi ns deploy & query prefix match (#280) 2023-04-08 17:43:02 +08:00
澄潭
7ff1d2c414 Update main.go 2023-04-07 18:23:39 +08:00
澄潭
acaf3d899a support higress rewrite annotaton (#278) 2023-04-07 17:59:13 +08:00
Xunzhuo
96e7153c8c ci: update latest release (#277)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-04-07 14:23:27 +08:00
Xunzhuo
0acb04fffb feat: add support for hgctl gateway-config to retrieve higress gateway config (#274)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-04-07 13:50:21 +08:00
Qianglin Li
affa1207d2 Fix the problem that ignoreUriCase does not work when the path type is prefix (#260)
Signed-off-by: charlie <qianglin98@qq.com>
2023-04-04 21:01:02 +08:00
Xunzhuo
e18557d2ea feat: add install hgctl hack script (#276)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-04-04 20:43:36 +08:00
Xunzhuo
0668eaea1e feat: add latest release of hgctl (#275)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-04-04 20:30:19 +08:00
Kent Dong
41f892b26d feat: Support enabling local deployment mode using "global.local" property (#269) 2023-04-04 19:46:26 +08:00
Xunzhuo
a5edad1a84 feat: init support hgctl (#273)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-04-04 19:38:44 +08:00
澄潭
0d4b8ee313 Update README.md 2023-04-03 17:25:50 +08:00
澄潭
5f32e159e5 Jwt policy compat (#266) 2023-04-03 16:14:42 +08:00
澄潭
7fd3f43c0d fix bug of port parsing in destination annotation (#262) 2023-03-30 19:24:55 +08:00
澄潭
c96ede21a5 refactor registry & add direct service discovery through staticIP or DNS (#261) 2023-03-29 18:45:57 +08:00
澄潭
87366aab49 Create SECURITY.md 2023-03-29 10:00:31 +08:00
澄潭
1a711bd267 Update issue templates 2023-03-29 09:56:54 +08:00
澄潭
d0d03e0e36 Delete BUG_REPORT.md 2023-03-29 09:55:35 +08:00
澄潭
755bcc2d58 Update issue templates 2023-03-29 09:55:13 +08:00
澄潭
42fddb6115 Update BUG_REPORT.md 2023-03-29 09:49:46 +08:00
onlypiglet
09e563cf9c doc: add basic_auth plugin english readme (#221) 2023-03-25 21:43:20 +08:00
澄潭
ef6912e466 Wasmplugin support disable config (#255) 2023-03-25 15:51:07 +08:00
casun18
07ce165661 doc: fix key_auth plugin readme (#256) 2023-03-25 15:50:20 +08:00
Qianglin Li
3844017bb9 add e2e test for canary weight (#245)
Signed-off-by: charlie <qianglin98@qq.com>
Co-authored-by: Xunzhuo <bitliu@tencent.com>
2023-03-24 17:46:20 +08:00
Yang
406b890a2a Add header control (#254) 2023-03-24 17:45:27 +08:00
澄潭
af31d455ed fix zk registry (#253) 2023-03-24 16:00:41 +08:00
澄潭
4a9e5aafd0 Add Charlie17Li to CODEOWNERS 2023-03-21 17:33:20 +08:00
Kent Dong
9dfabee26a feat: Support Wasm plugin OCI images with more than 2 layers (#244) 2023-03-20 13:00:33 +08:00
澄潭
19acbb4647 Update README_EN.md 2023-03-18 17:10:46 +08:00
澄潭
96ada21174 Update README.md 2023-03-18 17:05:15 +08:00
141 changed files with 14563 additions and 1001 deletions

View File

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

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Ask a question 💬
url: https://github.com/alibaba/higress/discussions
about: Ask a question or request support for using Higress.

View File

@@ -1,10 +1,15 @@
---
name: Bug Report
about: If you would like to report an issue to Higress, please use this template.
name: Non-{crash,security} bug
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**If you are reporting *any* crash or *any* potential security issue, *do not*
open an issue in this repo. Please report the issue via [ASRC](https://security.alibaba.com/)(Alibaba Security Response Center) where the issue will be triaged appropriately.**
- [ ] I have searched the [issues](https://github.com/alibaba/higress/issues) of this repository and believe that this is not a duplicate.
### . Issue Description
@@ -35,4 +40,4 @@ Just paste your stack trace here!
- Higress version:
- OS :
- Others:
- Others:

68
.github/workflows/latest-release.yaml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Latest Release
on:
push:
branches:
- "main"
jobs:
latest-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build hgctl latest multiarch binaries
run: |
GOPROXY="https://proxy.golang.org,direct" make build-hgctl-multiarch
tar -zcvf hgctl_latest_linux_amd64.tar.gz out/linux_amd64/
tar -zcvf hgctl_latest_linux_arm64.tar.gz out/linux_arm64/
tar -zcvf hgctl_latest_darwin_amd64.tar.gz out/darwin_amd64/
tar -zcvf hgctl_latest_darwin_arm64.tar.gz out/darwin_arm64/
# Ignore the error when we delete the latest release, it might not exist.
# GitHub APIs take sometime to make effect, we should make sure before Recreate the Latest Release and Tag,
# tag and release all get deleted. So we sleep sometime after deleting tag and release to wait for it taking effect.
- name: Delete the Latest Release
continue-on-error: true
run: |
gh release delete latest --repo $GITHUB_REPOSITORY
sleep 4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository_owner }}/${{ github.event.repository.name }}
# Ignore the error when we delete the latest tag, it might not exist.
- name: Delete the Latest Tag
continue-on-error: true
run: |
gh api --method DELETE /repos/$GITHUB_REPOSITORY/git/refs/tags/latest
sleep 4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository_owner }}/${{ github.event.repository.name }}
- name: Recreate the Latest Release and Tag
uses: softprops/action-gh-release@v1
with:
draft: false
prerelease: true
tag_name: latest
files: |
hgctl_latest_linux_amd64.tar.gz
hgctl_latest_linux_arm64.tar.gz
hgctl_latest_darwin_amd64.tar.gz
hgctl_latest_darwin_arm64.tar.gz
body: |
This is the "latest" release of **Higress**, which contains the most recent commits from the main branch.
This release **might not be stable**.
It is only intended for developers wishing to try out the latest features in Higress, some of which may not be fully implemented.
Try latest version of `hgctl` with:
``` shell
curl -Ls https://raw.githubusercontent.com/alibaba/higress/main/tools/hack/get-hgctl.sh | VERSION=latest bash
```

View File

@@ -26,6 +26,7 @@ header:
- 'VERSION'
- 'tools/'
- 'test/README.md'
- 'pkg/cmd/hgctl/testdata/config'
comment: on-failure
dependency:

View File

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

View File

@@ -6,13 +6,20 @@ export HUB ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
export CHARTS ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/charts
VERSION_PACKAGE := github.com/alibaba/higress/pkg/cmd/version
GIT_COMMIT:=$(shell git rev-parse HEAD)
GO_LDFLAGS += -X $(VERSION_PACKAGE).higressVersion=$(shell cat VERSION) \
-X $(VERSION_PACKAGE).gitCommitID=$(GIT_COMMIT)
GO ?= go
export GOPROXY ?= https://proxy.golang.com.cn,direct
GOARCH_LOCAL := $(TARGET_ARCH)
GOOS_LOCAL := $(TARGET_OS)
RELEASE_LDFLAGS='-extldflags -static -s -w'
RELEASE_LDFLAGS='$(GO_LDFLAGS) -extldflags -static -s -w'
export OUT:=$(TARGET_OUT)
export OUT_LINUX:=$(TARGET_OUT_LINUX)
@@ -32,7 +39,9 @@ endif
HIGRESS_DOCKER_BUILD_TOP:=${OUT_LINUX}/docker_build
BINARIES:=./cmd/higress
HIGRESS_BINARIES:=./cmd/higress
HGCTL_BINARIES:=./cmd/hgctl
$(OUT):
@mkdir -p $@
@@ -52,11 +61,26 @@ go.test.coverage: prebuild
.PHONY: build
build: prebuild $(OUT)
GOPROXY=$(GOPROXY) GOOS=$(GOOS_LOCAL) GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT)/ $(BINARIES)
GOPROXY=$(GOPROXY) GOOS=$(GOOS_LOCAL) GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT)/ $(HIGRESS_BINARIES)
.PHONY: build-linux
build-linux: prebuild $(OUT)
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT_LINUX)/ $(BINARIES)
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT_LINUX)/ $(HIGRESS_BINARIES)
.PHONY: build-hgctl
build-hgctl: $(OUT)
GOPROXY=$(GOPROXY) GOOS=$(GOOS_LOCAL) GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT)/ $(HGCTL_BINARIES)
.PHONY: build-linux-hgctl
build-linux-hgctl: $(OUT)
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT_LINUX)/ $(HGCTL_BINARIES)
.PHONY: build-hgctl-multiarch
build-hgctl-multiarch: $(OUT)
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=amd64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_amd64/ $(HGCTL_BINARIES)
GOPROXY=$(GOPROXY) GOOS=linux GOARCH=arm64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_arm64/ $(HGCTL_BINARIES)
GOPROXY=$(GOPROXY) GOOS=darwin GOARCH=amd64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/darwin_amd64/ $(HGCTL_BINARIES)
GOPROXY=$(GOPROXY) GOOS=darwin GOARCH=arm64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/darwin_arm64/ $(HGCTL_BINARIES)
# Create targets for OUT_LINUX/binary
# There are two use cases here:
@@ -73,14 +97,14 @@ $(OUT_LINUX)/$(shell basename $(1)): $(OUT_LINUX)
endif
endef
$(foreach bin,$(BINARIES),$(eval $(call build-linux,$(bin),"")))
$(foreach bin,$(HIGRESS_BINARIES),$(eval $(call build-linux,$(bin),"")))
# Create helper targets for each binary, like "pilot-discovery"
# As an optimization, these still build everything
$(foreach bin,$(BINARIES),$(shell basename $(bin))): build
$(foreach bin,$(HIGRESS_BINARIES),$(shell basename $(bin))): build
ifneq ($(OUT_LINUX),$(LOCAL_OUT))
# if we are on linux already, then this rule is handled by build-linux above, which handles BUILD_ALL variable
$(foreach bin,$(BINARIES),${LOCAL_OUT}/$(shell basename $(bin))): build
$(foreach bin,$(HIGRESS_BINARIES),${LOCAL_OUT}/$(shell basename $(bin))): build
endif
.PHONY: push
@@ -113,20 +137,20 @@ endef
install: pre-install
cd helm/higress; helm dependency build
helm install higress helm/higress -n higress-system --create-namespace --set 'global.kind=true'
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
ENVOY_LATEST_IMAGE_TAG ?= 0.7.0
ISTIO_LATEST_IMAGE_TAG ?= 0.7.0
ENVOY_LATEST_IMAGE_TAG ?= 1.0.0-rc
ISTIO_LATEST_IMAGE_TAG ?= 1.0.0-rc
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.kind=true'
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'
uninstall:
helm uninstall higress -n higress-system
upgrade: pre-install
cd helm/higress; helm dependency build
helm upgrade higress helm/higress -n higress-system --set 'global.kind=true'
helm upgrade higress helm/higress -n higress-system --set 'global.local=true'
helm-push:
cp api/kubernetes/customresourcedefinitions.gen.yaml helm/core/crds
@@ -185,7 +209,7 @@ delete-cluster: $(tools/kind) ## Delete kind cluster.
# kube-load-image loads a local built docker image into kube cluster.
.PHONY: kube-load-image
kube-load-image: $(tools/kind) ## Install the EG image to a kind cluster using the provided $IMAGE and $TAG.
kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster using the provided $IMAGE and $TAG.
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress $(TAG)
# run-ingress-e2e-test starts to run ingress e2e tests.
@@ -193,7 +217,7 @@ kube-load-image: $(tools/kind) ## Install the EG image to a kind cluster using t
run-ingress-e2e-test:
@echo -e "\n\033[36mRunning higress conformance tests...\033[0m"
@echo -e "\n\033[36mWaiting higress-controller to be ready...\033[0m\n"
kubectl wait --timeout=5m -n higress-system deployment/higress-controller --for=condition=Available
kubectl wait --timeout=10m -n higress-system deployment/higress-controller --for=condition=Available
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
kubectl wait --timeout=5m -n higress-system deployment/higress-gateway --for=condition=Available
kubectl wait --timeout=10m -n higress-system deployment/higress-gateway --for=condition=Available
go test -v -tags conformance ./test/ingress/e2e_test.go --ingress-class=higress --debug=true

166
README.md
View File

@@ -23,12 +23,14 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
![arch](https://img.alicdn.com/imgextra/i4/O1CN01OgGP1728t0xeRfRYJ_!!6000000007989-0-tps-1726-1366.jpg)
## Summary
- [**功能展示**](#功能展示)
- [**使用场景**](#使用场景)
- [**核心优势**](#核心优势)
- [**Quick Start**](#quick-start)
- [**Quick Start**](https://higress.io/zh-cn/docs/user/quickstart)
- [**社区**](#社区)
## 使用场景
- **Kubernetes Ingress 网关**:
@@ -73,164 +75,42 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
插件支持热更新,变更插件逻辑和配置都对流量无损。
## Quick Start
## 功能展示
- **丰富的可观测**
- [**本地环境**](#本地环境)
- [**生产环境**](#生产环境)
提供开箱即用的可观测Grafana&Prometheus 可以使用内置的也可对接自建的
### 本地环境
![](./docs/images/monitor.gif)
#### 第一步、 安装 kubectl & kind
- **插件扩展机制**
**MacOS**
官方提供了多种插件,用户也可以[开发](./plugins/wasm-go)自己的插件,构建成 docker/oci 镜像后在控制台配置,可以实时变更插件逻辑,对流量完全无损。
```bash
curl -Lo ./kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl
# for Intel Macs
[ $(uname -m) = x86_64 ]&& curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-darwin-amd64
# for M1 / ARM Macs
[ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-darwin-arm64
chmod +x ./kind ./kubectl
mv ./kind ./kubectl /some-dir-in-your-PATH/
```
![](./docs/images/plugin.gif)
**Windows 中使用 PowerShell:**
```bash
curl.exe -Lo kubectl.exe https://storage.googleapis.com/kubernetes-release/release/$(curl.exe -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/windows/amd64/kubectl.exe
curl.exe -Lo kind-windows-amd64.exe https://kind.sigs.k8s.io/dl/v0.17.0/kind-windows-amd64
Move-Item .\kind-windows-amd64.exe c:\some-dir-in-your-PATH\kind.exe
Move-Item .\kubectl.exe c:\some-dir-in-your-PATH\kubectl.exe
```
- **多种服务发现**
**Linux:**
默认提供 K8s Service 服务发现,通过配置可以对接 Nacos/ZooKeeper 等注册中心实现服务发现,也可以基于静态 IP 或者 DNS 来发现
```bash
curl -Lo ./kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-linux-amd64
chmod +x ./kind ./kubectl
sudo mv ./kind ./kubectl /usr/local/bin/kind
```
![](./docs/images/service-source.gif)
#### 第二步、 创建并启用 kind
- **域名和证书**
首先创建一个集群配置文件: `cluster.conf`
可以创建管理 TLS 证书,并配置域名的 HTTP/HTTPS 行为,域名策略里支持对特定域名生效插件
```yaml
# cluster.conf
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
```
![](./docs/images/domain.gif)
Mac & Linux 系统执行:
```bash
kind create cluster --name higress --config=cluster.conf
kubectl config use-context kind-higress
```
- **丰富的路由能力**
Windows 系统执行:
通过上面定义的服务发现机制,发现的服务会出现在服务列表中;创建路由时,选择域名,定义路由匹配机制,再选择目标服务进行路由;路由策略里支持对特定路由生效插件
```bash
kind.exe create cluster --name higress --config=cluster.conf
kubectl.exe config use-context kind-higress
```
![](./docs/images/route-service.gif)
#### 第三步、 安装 higress
```bash
helm repo add higress.io https://higress.io/helm-charts
helm install higress higress.io/higress -n higress-system --set global.kind=true --create-namespace --render-subchart-notes
```
helm版本需升级至**v3.8.0**及以上
#### 第四步、 创建 Ingress 资源并测试
```bash
kubectl apply -f https://higress.io/samples/quickstart.yaml
```
测试 Ingress 生效:
```bash
# should output "foo"
curl localhost/foo
# should output "bar"
curl localhost/bar
```
#### 卸载资源
```bash
kubectl delete -f https://higress.io/samples/quickstart.yaml
helm uninstall higress -n higress-system
kubectl delete ns higress-system
```
### 生产环境
#### 第一步、 安装 higress
```bash
helm repo add higress.io https://higress.io/helm-charts
helm install higress higress.io/higress -n higress-system --create-namespace --render-subchart-notes
```
#### 第二步、 创建 Ingress 资源并测试
假设在 default 命名空间下已经部署了一个 test service服务端口为 80 ,则创建下面这个 K8s Ingress
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-example
spec:
ingressClassName: higress
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: test
port:
number: 80
```
测试能访问到该服务:
```bash
curl "$(k get svc -n higress-system higress-gateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')"/foo -H 'host: foo.bar.com'
```
#### 卸载资源
```bash
helm uninstall higress -n higress-system
kubectl delete ns higress-system
```
## 社区

View File

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

14
SECURITY.md Normal file
View File

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

View File

@@ -1 +1 @@
v0.7.0
v1.0.0-rc

View File

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

View File

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

View File

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

29
cmd/hgctl/main.go Normal file
View File

@@ -0,0 +1,29 @@
// 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"
"os"
"github.com/alibaba/higress/pkg/cmd/hgctl"
)
func main() {
if err := hgctl.GetRootCommand().Execute(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@@ -17,119 +17,13 @@ package main
import (
"fmt"
"os"
"time"
"github.com/spf13/cobra"
"istio.io/istio/pilot/pkg/features"
"istio.io/istio/pkg/cmd"
"istio.io/istio/pkg/config/constants"
"istio.io/istio/pkg/keepalive"
"istio.io/pkg/log"
"istio.io/pkg/version"
"github.com/alibaba/higress/pkg/bootstrap"
innerconstants "github.com/alibaba/higress/pkg/config/constants"
"github.com/alibaba/higress/pkg/cmd"
)
var (
serverArgs *bootstrap.ServerArgs
loggingOptions = log.DefaultOptions()
serverProvider = func(args *bootstrap.ServerArgs) (bootstrap.ServerInterface, error) {
return bootstrap.NewServer(serverArgs)
}
waitForMonitorSignal = func(stop chan struct{}) {
cmd.WaitSignal(stop)
}
rootCmd = &cobra.Command{
Use: "higress",
}
serveCmd = &cobra.Command{
Use: "serve",
Aliases: []string{"serve"},
Short: "Starts the higress ingress controller",
Example: "higress serve",
PreRunE: func(c *cobra.Command, args []string) error {
return log.Configure(loggingOptions)
},
RunE: func(c *cobra.Command, args []string) error {
cmd.PrintFlags(c.Flags())
log.Infof("Version %s", version.Info.String())
stop := make(chan struct{})
server, err := serverProvider(serverArgs)
if err != nil {
return fmt.Errorf("fail to create higress server: %v", err)
}
if err := server.Start(stop); err != nil {
return fmt.Errorf("fail to start higress server: %v", err)
}
waitForMonitorSignal(stop)
server.WaitUntilCompletion()
return nil
},
}
)
func init() {
serverArgs = &bootstrap.ServerArgs{
Debug: true,
NativeIstio: true,
HttpAddress: ":8888",
GrpcAddress: ":15051",
GrpcKeepAliveOptions: keepalive.DefaultOption(),
XdsOptions: bootstrap.XdsOptions{
DebounceAfter: features.DebounceAfter,
DebounceMax: features.DebounceMax,
EnableEDSDebounce: features.EnableEDSDebounce,
},
}
serveCmd.PersistentFlags().StringVar(&serverArgs.GatewaySelectorKey, "gatewaySelectorKey", "higress", "gateway resource selector label key")
serveCmd.PersistentFlags().StringVar(&serverArgs.GatewaySelectorValue, "gatewaySelectorValue", "higress-gateway", "gateway resource selector label value")
serveCmd.PersistentFlags().BoolVar(&serverArgs.EnableStatus, "enableStatus", true, "enable the ingress status syncer which use to update the ip in ingress's status")
serveCmd.PersistentFlags().StringVar(&serverArgs.IngressClass, "ingressClass", innerconstants.DefaultIngressClass, "if not empty, only watch the ingresses have the specified class, otherwise watch all ingresses")
serveCmd.PersistentFlags().StringVar(&serverArgs.WatchNamespace, "watchNamespace", "", "if not empty, only wath the ingresses in the specified namespace, otherwise watch in all namespacees")
serveCmd.PersistentFlags().BoolVar(&serverArgs.Debug, "debug", serverArgs.Debug, "if true, enables more debug http api")
serveCmd.PersistentFlags().StringVar(&serverArgs.HttpAddress, "httpAddress", serverArgs.HttpAddress, "the http address")
serveCmd.PersistentFlags().StringVar(&serverArgs.GrpcAddress, "grpcAddress", serverArgs.GrpcAddress, "the grpc address")
serveCmd.PersistentFlags().BoolVar(&serverArgs.KeepStaleWhenEmpty, "keepStaleWhenEmpty", false, "keep the stale service entry when there are no endpoints in the service")
serveCmd.PersistentFlags().StringVar(&serverArgs.RegistryOptions.ClusterRegistriesNamespace, "clusterRegistriesNamespace",
serverArgs.RegistryOptions.ClusterRegistriesNamespace, "Namespace for ConfigMap which stores clusters configs")
serveCmd.PersistentFlags().StringVar(&serverArgs.RegistryOptions.KubeConfig, "kubeconfig", "",
"Use a Kubernetes configuration file instead of in-cluster configuration")
// RegistryOptions Controller options
serveCmd.PersistentFlags().DurationVar(&serverArgs.RegistryOptions.KubeOptions.ResyncPeriod, "resync", 60*time.Second,
"Controller resync interval")
serveCmd.PersistentFlags().StringVar(&serverArgs.RegistryOptions.KubeOptions.DomainSuffix, "domain", constants.DefaultKubernetesDomain,
"DNS domain suffix")
serveCmd.PersistentFlags().StringVar((*string)(&serverArgs.RegistryOptions.KubeOptions.ClusterID), "clusterID", "Kubernetes",
"The ID of the cluster that this instance resides")
serveCmd.PersistentFlags().StringToStringVar(&serverArgs.RegistryOptions.KubeOptions.ClusterAliases, "clusterAliases", map[string]string{},
"Alias names for clusters")
serveCmd.PersistentFlags().Float32Var(&serverArgs.RegistryOptions.KubeOptions.KubernetesAPIQPS, "kubernetesApiQPS", 80.0,
"Maximum QPS when communicating with the kubernetes API")
serveCmd.PersistentFlags().IntVar(&serverArgs.RegistryOptions.KubeOptions.KubernetesAPIBurst, "kubernetesApiBurst", 160,
"Maximum burst for throttle when communicating with the kubernetes API")
loggingOptions.AttachCobraFlags(serveCmd)
serverArgs.GrpcKeepAliveOptions.AttachCobraFlags(serveCmd)
rootCmd.AddCommand(serveCmd)
}
func main() {
log.EnableKlogWithCobra()
if err := rootCmd.Execute(); err != nil {
log.Error(err)
os.Exit(-1)
if err := cmd.GetRootCommand().Execute(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

BIN
docs/images/domain.gif Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
docs/images/monitor.gif Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

BIN
docs/images/plugin.gif Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -0,0 +1,55 @@
diff -Naur envoy/bazel/envoy_binary.bzl envoy-new/bazel/envoy_binary.bzl
--- envoy/bazel/envoy_binary.bzl 2023-04-08 20:52:57.041729111 +0800
+++ envoy-new/bazel/envoy_binary.bzl 2023-04-08 20:50:53.657603065 +0800
@@ -80,7 +80,7 @@
"@envoy//bazel:boringssl_fips": [],
"@envoy//bazel:windows_x86_64": [],
"//conditions:default": ["-pie"],
- }) + _envoy_select_exported_symbols(["-Wl,-E"])
+ }) + _envoy_select_exported_symbols(["-Wl,-E"]) + envoy_select_alimesh(["-lcrypt"])
def _envoy_stamped_deps():
return select({
diff -Naur envoy/bazel/repositories.bzl envoy-new/bazel/repositories.bzl
--- envoy/bazel/repositories.bzl 2023-04-08 20:52:57.085730582 +0800
+++ envoy-new/bazel/repositories.bzl 2023-04-08 20:27:20.110335884 +0800
@@ -272,6 +272,8 @@
actual = "@bazel_tools//tools/cpp/runfiles",
)
+ _com_github_higress_wasm_extensions()
+
def _boringssl():
external_http_archive(
name = "boringssl",
@@ -1066,6 +1068,17 @@
actual = "@com_github_wasm_c_api//:wasmtime_lib",
)
+def _com_github_higress_wasm_extensions():
+ native.local_repository(
+ name = "com_github_higress_wasm_extensions",
+ path = "../../wasm-cpp",
+ )
+
+ native.bind(
+ name = "basic_auth_lib",
+ actual = "@com_github_higress_wasm_extensions//extensions/basic_auth:basic_auth_lib",
+ )
+
def _rules_fuzzing():
external_http_archive(
name = "rules_fuzzing",
diff -Naur envoy/source/exe/BUILD envoy-new/source/exe/BUILD
--- envoy/source/exe/BUILD 2023-04-08 20:52:57.053729512 +0800
+++ envoy-new/source/exe/BUILD 2023-04-08 19:48:37.420667254 +0800
@@ -43,6 +43,9 @@
"//bazel:darwin": envoy_all_extensions(DARWIN_SKIP_TARGETS),
"//conditions:default": envoy_all_extensions(),
}),
+ alimesh_deps = [
+ "//external:basic_auth_lib",
+ ],
)
envoy_cc_library(

12
go.mod
View File

@@ -27,11 +27,14 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/nacos-group/nacos-sdk-go v1.0.8
github.com/nacos-group/nacos-sdk-go/v2 v2.1.2
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
go.uber.org/atomic v1.9.0
google.golang.org/grpc v1.48.0
google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v2 v2.4.0
istio.io/api v0.0.0-20211122181927-8da52c66ff23
istio.io/client-go v1.12.0-rc.1.0.20211118171212-b744b6f111e4
istio.io/gogo-genproto v0.0.0-20211115195057-0e34bdd2be67
@@ -39,8 +42,11 @@ require (
istio.io/pkg v0.0.0-20211115195056-e379f31ee62a
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
k8s.io/cli-runtime v0.22.2
k8s.io/client-go v0.22.2
k8s.io/kubectl v0.22.2
sigs.k8s.io/controller-runtime v0.10.2
sigs.k8s.io/yaml v1.3.0
)
require (
@@ -142,7 +148,6 @@ require (
github.com/opencontainers/runc v1.0.2 // indirect
github.com/openshift/api v0.0.0-20200713203337-b2494ecb17dd // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
@@ -154,7 +159,6 @@ require (
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
github.com/yl2chen/cidranger v1.0.2 // indirect
@@ -181,21 +185,17 @@ require (
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.22.2 // indirect
k8s.io/cli-runtime v0.22.2 // indirect
k8s.io/component-base v0.22.2 // indirect
k8s.io/klog/v2 v2.10.0 // indirect
k8s.io/kube-openapi v0.0.0-20211020163157-7327e2aaee2b // indirect
k8s.io/kubectl v0.22.2 // indirect
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
sigs.k8s.io/gateway-api v0.4.0 // indirect
sigs.k8s.io/kustomize/api v0.8.11 // indirect
sigs.k8s.io/kustomize/kyaml v0.11.0 // indirect
sigs.k8s.io/mcs-api v0.1.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace github.com/dubbogo/gost => github.com/johnlanni/gost v1.11.23-0.20220713132522-0967a24036c6

View File

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

View File

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

View File

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

View File

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

View File

@@ -80,7 +80,7 @@ spec:
- name: REVISION
value: "{{ .Values.revision | default `default` }}"
- name: JWT_POLICY
value: {{ .Values.global.jwtPolicy }}
value: {{ include "controller.jwtPolicy" . }}
- name: PILOT_CERT_PROVIDER
value: "istiod"
- name: POD_NAME
@@ -128,7 +128,7 @@ spec:
- name: CUSTOM_CA_CERT_NAME
value: "higress-ca-root-cert"
{{- end }}
{{- if not .Values.global.kind }}
{{- if not (or .Values.global.local .Values.global.kind) }}
resources:
{{- if .Values.pilot.resources }}
{{ toYaml .Values.pilot.resources | trim | indent 12 }}
@@ -147,7 +147,7 @@ spec:
volumeMounts:
- name: config
mountPath: /etc/istio/config
{{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
mountPath: /var/run/secrets/tokens
readOnly: true
@@ -173,12 +173,12 @@ spec:
- "serve"
- --gatewaySelectorKey=higress
- --gatewaySelectorValue={{ .Release.Namespace }}-{{ include "gateway.name" . }}
{{- if not .Values.enableStatus }}
- --enableStatus={{ .Values.enableStatus }}
{{- if not .Values.global.enableStatus }}
- --enableStatus={{ .Values.global.enableStatus }}
{{- end }}
- --ingressClass={{ .Values.ingressClass }}
{{- if .Values.watchNamespace }}
- --watchNamespace={{ .Values.watchNamespace }}
- --ingressClass={{ .Values.global.ingressClass }}
{{- if .Values.global.watchNamespace }}
- --watchNamespace={{ .Values.global.watchNamespace }}
{{- end }}
env:
- name: POD_NAME
@@ -210,7 +210,7 @@ spec:
{{- end }}
readinessProbe:
{{- toYaml .Values.controller.probe | nindent 12 }}
{{- if not .Values.global.kind }}
{{- if not (or .Values.global.local .Values.global.kind) }}
resources:
{{- toYaml .Values.controller.resources | nindent 12 }}
{{- end }}
@@ -241,7 +241,7 @@ spec:
- emptyDir:
medium: Memory
name: local-certs
{{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
projected:
sources:
@@ -249,6 +249,7 @@ spec:
audience: {{ .Values.global.sds.token.aud }}
expirationSeconds: 43200
path: istio-token
{{- end }}
# Optional: user-generated root
- name: cacerts
secret:
@@ -264,4 +265,3 @@ spec:
name: pilot-jwks-extra-cacerts{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -1,3 +1,13 @@
{{- $unprivilegedPortSupported := true }}
{{- range $index, $node := (lookup "v1" "Node" "default" "").items }}
{{- $kernelVersion := $node.status.nodeInfo.kernelVersion }}
{{- if $kernelVersion }}
{{- $kernelVersion = regexFind "^(\\d+\\.\\d+\\.\\d+)" $kernelVersion }}
{{- if and $kernelVersion (semverCompare "<4.11.0" $kernelVersion) }}
{{- $unprivilegedPortSupported = false }}
{{- end }}
{{- end }}
{{- end -}}
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -9,7 +19,7 @@ metadata:
{{- .Values.gateway.annotations | toYaml | nindent 4 }}
spec:
{{- if not .Values.gateway.autoscaling.enabled }}
{{- if not .Values.global.kind }}
{{- if not (or .Values.global.local .Values.global.kind) }}
replicas: {{ .Values.gateway.replicas }}
{{- else }}
replicas: 1
@@ -21,7 +31,7 @@ spec:
strategy:
rollingUpdate:
maxSurge: {{ .Values.gateway.rollingMaxSurge }}
{{- if .Values.global.kind }}
{{- if or .Values.global.local .Values.global.kind }}
maxUnavailable: 100%
{{- else }}
maxUnavailable: {{ .Values.gateway.rollingMaxUnavailable }}
@@ -50,7 +60,7 @@ spec:
securityContext:
{{- if .Values.gateway.securityContext }}
{{- toYaml .Values.gateway.securityContext | nindent 8 }}
{{- else if and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion) }}
{{- else if and $unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
sysctls:
- name: net.ipv4.ip_unprivileged_port_start
@@ -71,7 +81,7 @@ spec:
securityContext:
{{- if .Values.gateway.containerSecurityContext }}
{{- toYaml .Values.gateway.containerSecurityContext | nindent 12 }}
{{- else if and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion) }}
{{- else if and $unprivilegedPortSupported (and (not .Values.gateway.hostNetwork) (semverCompare ">=1.22-0" .Capabilities.KubeVersion.GitVersion)) }}
# Safe since 1.22: https://github.com/kubernetes/kubernetes/pull/103326
capabilities:
drop:
@@ -129,7 +139,7 @@ spec:
- name: ENABLE_INGRESS_GATEWAY_SDS
value: "false"
- name: JWT_POLICY
value: {{ .Values.global.jwtPolicy }}
value: {{ include "controller.jwtPolicy" . }}
- name: ISTIO_META_HTTP10
value: "1"
- name: ISTIO_META_CLUSTER_ID
@@ -152,7 +162,7 @@ spec:
- containerPort: 15090
protocol: TCP
name: http-envoy-prom
{{- if .Values.global.kind }}
{{- if or .Values.global.local .Values.global.kind }}
- containerPort: 80
hostPort: 80
name: http
@@ -172,12 +182,12 @@ spec:
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 3
{{- if not .Values.global.kind }}
{{- if not (or .Values.global.local .Values.global.kind) }}
resources:
{{- toYaml .Values.gateway.resources | nindent 12 }}
{{- end }}
volumeMounts:
{{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
mountPath: /var/run/secrets/tokens
readOnly: true
@@ -213,7 +223,7 @@ spec:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
{{- if eq (include "controller.jwtPolicy" .) "third-party-jwt" }}
- name: istio-token
projected:
sources:

View File

@@ -1,9 +1,21 @@
revision: ""
global:
# IngressClass filters which ingress resources the higress controller watches.
# The default ingress class is higress.
# There are some special cases for special ingress class.
# 1. When the ingress class is set as nginx, the higress controller will watch ingress
# resources with the nginx ingress class or without any ingress class.
# 2. When the ingress class is set empty, the higress controller will watch all ingress
# resources in the k8s cluster.
ingressClass: "higress"
watchNamespace: ""
disableAlpnH2: true
enableStatus: true
# whether to use autoscaling/v2 template for HPA settings
# for internal usage only, not to be configured by users.
autoscalingv2API: true
kind: false
local: false # When deploying to a local cluster (e.g.: kind cluster), set this to true.
kind: false # Deprecated. Please use "global.local" instead. Will be removed later.
enableIstioAPI: false
# Deprecated
enableHigressIstio: false
@@ -33,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: 0.7.0
tag: 1.0.0-rc
# Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
@@ -322,16 +334,6 @@ global:
caName: ""
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
# IngressClass filters which ingress resources the higress controller watches.
# The default ingress class is higress.
# There are some special cases for special ingress class.
# 1. When the ingress class is set as nginx, the higress controller will watch ingress
# resources with the nginx ingress class or without any ingress class.
# 2. When the ingress class is set empty, the higress controller will watch all ingress
# resources in the k8s cluster.
ingressClass: "higress"
watchNamespace: ""
enableStatus: true
clusterName: ""
# meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior
# See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options
@@ -367,7 +369,7 @@ gateway:
name: "higress-gateway"
replicas: 2
image: gateway
tag: "0.7.0"
tag: "1.0.0-rc"
# revision declares which revision this gateway is a part of
revision: ""
@@ -455,7 +457,7 @@ controller:
name: "higress-controller"
replicas: 1
image: higress
tag: "0.7.0"
tag: "1.0.0-rc"
env: {}
labels: {}
@@ -545,7 +547,7 @@ pilot:
rollingMaxUnavailable: 25%
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
tag: 0.7.0
tag: 1.0.0-rc
# Can be a full hub/image:tag
image: pilot

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 0.7.0
version: 1.0.0-rc
- name: higress-console
repository: https://higress.io/helm-charts/
version: 0.1.0
digest: sha256:3fd6cfb0fd10178927569d57e0cbff5250870dd677cbf30995c49ced83e38f7c
generated: "2023-03-15T15:09:02.878072+08:00"
version: 0.2.0
digest: sha256:0a34765ab2125ccf397e81566b4d81a8dc0742a2477d225aad77d9450e4add94
generated: "2023-04-08T23:17:37.193119+08:00"

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
diff -Naur istio/pilot/pkg/networking/core/v1alpha3/route/route.go istio-new/pilot/pkg/networking/core/v1alpha3/route/route.go
--- istio/pilot/pkg/networking/core/v1alpha3/route/route.go 2023-04-08 16:02:02.000000000 +0800
+++ istio-new/pilot/pkg/networking/core/v1alpha3/route/route.go 2023-04-07 18:19:20.000000000 +0800
@@ -1049,6 +1049,10 @@
out.QueryParameterMatchSpecifier = &route.QueryParameterMatcher_StringMatch{
StringMatch: &matcher.StringMatcher{MatchPattern: &matcher.StringMatcher_Exact{Exact: m.Exact}},
}
+ case *networking.StringMatch_Prefix:
+ out.QueryParameterMatchSpecifier = &route.QueryParameterMatcher_StringMatch{
+ StringMatch: &matcher.StringMatcher{MatchPattern: &matcher.StringMatcher_Prefix{Prefix: m.Prefix}},
+ }
case *networking.StringMatch_Regex:
out.QueryParameterMatchSpecifier = &route.QueryParameterMatcher_StringMatch{
StringMatch: &matcher.StringMatcher{
diff -Naur istio/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go istio-new/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go
--- istio/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go 2023-04-08 16:02:02.000000000 +0800
+++ istio-new/pilot/pkg/serviceregistry/kube/controller/namespacecontroller.go 2023-04-08 14:35:57.000000000 +0800
@@ -16,6 +16,7 @@
import (
"fmt"
+ "os"
"time"
v1 "k8s.io/api/core/v1"
@@ -171,9 +172,16 @@
return k8s.InsertDataToConfigMap(nc.client, nc.configmapLister, meta, nc.caBundleWatcher.GetCABundle())
}
+var podNs = os.Getenv("POD_NAMESPACE")
+
// On namespace change, update the config map.
// If terminating, this will be skipped
func (nc *NamespaceController) namespaceChange(ns *v1.Namespace) {
+ // Added by ingress
+ if ns.Name != podNs {
+ return
+ }
+ // End added by ingress
if ns.Status.Phase != v1.NamespaceTerminating {
nc.syncNamespace(ns.Name)
}
@@ -186,6 +194,11 @@
log.Errorf("failed to convert to configmap: %v", err)
return
}
+ // Added by ingress
+ if cm.Namespace != podNs {
+ return
+ }
+ // End added by ingress
// This is a change to a configmap we don't watch, ignore it
if cm.Name != dynamicCACertNamespaceConfigMap {
return

View File

@@ -0,0 +1,19 @@
diff -Naur proxy/common/scripts/run.sh proxy-new/common/scripts/run.sh
--- proxy/common/scripts/run.sh 2023-04-08 21:12:05.896147208 +0800
+++ proxy-new/common/scripts/run.sh 2023-04-08 20:33:51.935437889 +0800
@@ -40,6 +40,7 @@
MOUNT_ENVOY_SOURCE="${MOUNT_ENVOY_SOURCE:-`cd $MOUNT_SOURCE/../envoy;pwd`}"
MOUNT_PACKAGE_SOURCE="${MOUNT_PACKAGE_SOURCE:-`cd $MOUNT_SOURCE/../package;pwd`}"
MOUNT_ROOT_SOURCE="${MOUNT_ROOT_SOURCE:-`cd $MOUNT_SOURCE/..;pwd`}"
+MOUNT_PLUGINS_SOURCE="${MOUNT_PLUGINS_SOURCE:-`cd $MOUNT_SOURCE/../../plugins/wasm-cpp;pwd`}"
read -ra DOCKER_RUN_OPTIONS <<< "${DOCKER_RUN_OPTIONS:-}"
@@ -64,6 +65,7 @@
--mount "type=bind,source=${MOUNT_SOURCE},destination=/work" \
--mount "type=bind,source=${MOUNT_ROOT_SOURCE}/..,destination=/parent" \
--mount "type=bind,source=${MOUNT_ENVOY_SOURCE},destination=/envoy" \
+ --mount "type=bind,source=${MOUNT_PLUGINS_SOURCE},destination=/wasm-cpp" \
--mount "type=volume,source=go,destination=/go" \
--mount "type=volume,source=gocache,destination=/gocache" \
--mount "type=volume,source=cache,destination=/home/.cache" \

View File

@@ -0,0 +1,65 @@
// 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 hgctl
import (
"fmt"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
func bootstrapConfigCmd() *cobra.Command {
configCmd := &cobra.Command{
Use: "bootstrap <pod-name>",
Aliases: []string{"b"},
Short: "Retrieves bootstrap Envoy xDS resources from the specified Higress Gateway Pod",
Long: `Retrieves information about bootstrap Envoy xDS resources from the specified Higress Gateway Pod`,
Example: ` # Retrieve summary about bootstrap configuration for a given pod from Envoy.
hgctl gateway-config bootstrap <pod-name> -n <pod-namespace>
# Retrieve full configuration dump as YAML
hgctl gateway-config bootstrap <pod-name> -n <pod-namespace> -o yaml
# Retrieve full configuration dump with short syntax
hgctl gc b <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runBootstrapConfig(c, args))
},
}
return configCmd
}
func runBootstrapConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if err != nil {
return err
}
bootstrap, err := GetXDSResource(BootstrapEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(bootstrap, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

View File

@@ -0,0 +1,64 @@
// 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 hgctl
import (
"fmt"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
func clusterConfigCmd() *cobra.Command {
configCmd := &cobra.Command{
Use: "cluster <pod-name>",
Short: "Retrieves cluster Envoy xDS resources from the specified Higress Gateway Pod",
Aliases: []string{"c"},
Long: `Retrieves information about cluster Envoy xDS resources from the specified Higress Gateway Pod`,
Example: ` # Retrieve summary about cluster configuration for a given pod from Envoy.
hgctl gateway-config cluster <pod-name> -n <pod-namespace>
# Retrieve full configuration dump as YAML
hgctl gateway-config cluster <pod-name> -n <pod-namespace> -o yaml
# Retrieve full configuration dump with short syntax
hgctl gc c <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runClusterConfig(c, args))
},
}
return configCmd
}
func runClusterConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if err != nil {
return err
}
cluster, err := GetXDSResource(ClusterEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(cluster, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

View File

@@ -0,0 +1,79 @@
// 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 hgctl
import (
"fmt"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
func newConfigCommand() *cobra.Command {
cfgCommand := &cobra.Command{
Use: "gateway-config",
Aliases: []string{"gc"},
Short: "Retrieve Higress Gateway configuration.",
Long: "Retrieve information about Higress Gateway Configuration.",
}
cfgCommand.AddCommand(allConfigCmd())
cfgCommand.AddCommand(bootstrapConfigCmd())
cfgCommand.AddCommand(clusterConfigCmd())
cfgCommand.AddCommand(endpointConfigCmd())
cfgCommand.AddCommand(listenerConfigCmd())
cfgCommand.AddCommand(routeConfigCmd())
flags := cfgCommand.Flags()
options.AddKubeConfigFlags(flags)
cfgCommand.PersistentFlags().StringVarP(&output, "output", "o", "json", "One of 'yaml' or 'json'")
cfgCommand.PersistentFlags().StringVarP(&podNamespace, "namespace", "n", "higress-system", "Namespace where envoy proxy pod are installed.")
return cfgCommand
}
func allConfigCmd() *cobra.Command {
configCmd := &cobra.Command{
Use: "all <pod-name>",
Short: "Retrieves all Envoy xDS resources from the specified Higress Gateway Pod",
Long: `Retrieves information about all Envoy xDS resources from the specified Higress Gateway Pod`,
Example: ` # Retrieve summary about all configuration for a given pod from Envoy.
hgctl gateway-config all <pod-name> -n <pod-namespace>
# Retrieve full configuration dump as YAML
hgctl gateway-config all <pod-name> -n <pod-namespace> -o yaml
# Retrieve full configuration dump with short syntax
hgctl gc all <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runAllConfig(c, args))
},
}
return configCmd
}
func runAllConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, true)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(configDump))
return err
}

View File

@@ -0,0 +1,65 @@
// 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 hgctl
import (
"fmt"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
func endpointConfigCmd() *cobra.Command {
configCmd := &cobra.Command{
Use: "endpoint <pod-name>",
Short: "Retrieves endpoint Envoy xDS resources from the specified Higress Gateway Pod",
Aliases: []string{"e"},
Long: `Retrieves information about endpoint Envoy xDS resources from the specified Higress Gateway Pod`,
Example: ` # Retrieve summary about endpoint configuration for a given pod from Envoy.
hgctl gateway-config endpoint <pod-name> -n <pod-namespace>
# Retrieve configuration dump as YAML
hgctl gateway-config endpoint <pod-name> -n <pod-namespace> -o yaml
# Retrieve configuration dump with short syntax
hgctl gc e <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runEndpointConfig(c, args))
},
}
return configCmd
}
func runEndpointConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, true)
if err != nil {
return err
}
endpoint, err := GetXDSResource(EndpointEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(endpoint, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

View File

@@ -0,0 +1,65 @@
// 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 hgctl
import (
"fmt"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
func listenerConfigCmd() *cobra.Command {
configCmd := &cobra.Command{
Use: "listener <pod-name>",
Aliases: []string{"l"},
Short: "Retrieves listener Envoy xDS resources from the specified Higress Gateway Pod",
Long: `Retrieves information about listener Envoy xDS resources from the specified Higress Gateway Pod`,
Example: ` # Retrieve summary about listener configuration for a given pod from Envoy.
hgctl gateway-config listener <pod-name> -n <pod-namespace>
# Retrieve full configuration dump as YAML
hgctl gateway-config listener <pod-name> -n <pod-namespace> -o yaml
# Retrieve full configuration dump with short syntax
hgctl gc l <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runListenerConfig(c, args))
},
}
return configCmd
}
func runListenerConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if err != nil {
return err
}
listener, err := GetXDSResource(ListenerEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(listener, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

View File

@@ -0,0 +1,151 @@
// 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 hgctl
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/yaml"
)
var (
output string
podName string
podNamespace string
)
const (
adminPort = 15000
containerName = "envoy"
)
func retrieveConfigDump(args []string, includeEds bool) ([]byte, error) {
if len(args) != 0 {
podName = args[0]
}
if podNamespace == "" {
return nil, fmt.Errorf("pod namespace is required")
}
if podName == "" || len(args) == 0 {
c, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return nil, fmt.Errorf("failed to build kubernetes client: %w", err)
}
podList, err := c.PodsForSelector(podNamespace, "app=higress-gateway")
if err != nil {
return nil, err
}
if len(podList.Items) == 0 {
return nil, fmt.Errorf("higress gateway pod is not existed in namespace %s", podNamespace)
}
podName = podList.Items[0].GetName()
}
fw, err := portForwarder(types.NamespacedName{
Namespace: podNamespace,
Name: podName,
})
if err != nil {
return nil, err
}
if err := fw.Start(); err != nil {
return nil, err
}
defer fw.Stop()
configDump, err := fetchGatewayConfig(fw, includeEds)
if err != nil {
return nil, err
}
return configDump, nil
}
func portForwarder(nn types.NamespacedName) (kubernetes.PortForwarder, error) {
c, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return nil, fmt.Errorf("build CLI client fail: %w", err)
}
pod, err := c.Pod(nn)
if err != nil {
return nil, fmt.Errorf("get pod %s fail: %w", nn, err)
}
if pod.Status.Phase != "Running" {
return nil, fmt.Errorf("pod %s is not running", nn)
}
fw, err := kubernetes.NewLocalPortForwarder(c, nn, 0, adminPort)
if err != nil {
return nil, err
}
return fw, nil
}
func formatGatewayConfig(configDump any, output string) ([]byte, error) {
out, err := json.MarshalIndent(configDump, "", " ")
if err != nil {
return nil, err
}
if output == "yaml" {
out, err = yaml.JSONToYAML(out)
if err != nil {
return nil, err
}
}
return out, nil
}
func fetchGatewayConfig(fw kubernetes.PortForwarder, includeEds bool) ([]byte, error) {
out, err := configDumpRequest(fw.Address(), includeEds)
if err != nil {
return nil, err
}
return out, nil
}
func configDumpRequest(address string, includeEds bool) ([]byte, error) {
url := fmt.Sprintf("http://%s/config_dump", address)
if includeEds {
url = fmt.Sprintf("%s?include_eds", url)
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer func() {
_ = resp.Body.Close()
}()
return io.ReadAll(resp.Body)
}

View File

@@ -0,0 +1,65 @@
// 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 hgctl
import (
"fmt"
"github.com/spf13/cobra"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
func routeConfigCmd() *cobra.Command {
configCmd := &cobra.Command{
Use: "route <pod-name>",
Aliases: []string{"r"},
Short: "Retrieves route Envoy xDS resources from the specified Higress Gateway Pod",
Long: `Retrieves information about route Envoy xDS resources from the specified Higress Gateway Pod`,
Example: ` # Retrieve summary about route configuration for a given pod from Envoy.
hgctl gateway-config route <pod-name> -n <pod-namespace>
# Retrieve full configuration dump as YAML
hgctl gateway-config route <pod-name> -n <pod-namespace> -o yaml
# Retrieve full configuration dump with short syntax
hgctl gc r <pod-name> -n <pod-namespace>
`,
Run: func(c *cobra.Command, args []string) {
cmdutil.CheckErr(runRouteConfig(c, args))
},
}
return configCmd
}
func runRouteConfig(c *cobra.Command, args []string) error {
configDump, err := retrieveConfigDump(args, false)
if err != nil {
return err
}
route, err := GetXDSResource(RouteEnvoyConfigType, configDump)
if err != nil {
return err
}
out, err := formatGatewayConfig(route, output)
if err != nil {
return err
}
_, err = fmt.Fprintln(c.OutOrStdout(), string(out))
return err
}

View File

@@ -0,0 +1,220 @@
// 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 hgctl
import (
"fmt"
"log"
"net"
"net/http"
"os"
"path"
"testing"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/stretchr/testify/assert"
)
var _ kubernetes.PortForwarder = &fakePortForwarder{}
type fakePortForwarder struct {
responseBody []byte
localPort int
l net.Listener
mux *http.ServeMux
}
func newFakePortForwarder(b []byte) (kubernetes.PortForwarder, error) {
p, err := kubernetes.LocalAvailablePort()
if err != nil {
return nil, err
}
fw := &fakePortForwarder{
responseBody: b,
localPort: p,
mux: http.NewServeMux(),
}
fw.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(fw.responseBody)
})
return fw, nil
}
func (fw *fakePortForwarder) Start() error {
l, err := net.Listen("tcp", fw.Address())
if err != nil {
return err
}
fw.l = l
go func() {
if err := http.Serve(l, fw.mux); err != nil {
log.Fatal(err)
}
}()
return nil
}
func (fw *fakePortForwarder) Stop() {}
func (fw *fakePortForwarder) Address() string {
return fmt.Sprintf("localhost:%d", fw.localPort)
}
func TestExtractAllConfigDump(t *testing.T) {
input, err := readInputConfig("in.all.json")
assert.NoError(t, err)
fw, err := newFakePortForwarder(input)
assert.NoError(t, err)
err = fw.Start()
assert.NoError(t, err)
cases := []struct {
output string
expected string
resourceType string
}{
{
output: "json",
expected: "out.all.json",
},
{
output: "yaml",
expected: "out.all.yaml",
},
}
for _, tc := range cases {
t.Run(tc.output, func(t *testing.T) {
configDump, err := fetchGatewayConfig(fw, true)
assert.NoError(t, err)
data, err := GetXDSResource(AllEnvoyConfigType, configDump)
assert.NoError(t, err)
got, err := formatGatewayConfig(data, tc.output)
assert.NoError(t, err)
out, err := readOutputConfig(tc.expected)
assert.NoError(t, err)
if tc.output == "yaml" {
assert.YAMLEq(t, string(out), string(got))
} else {
assert.JSONEq(t, string(out), string(got))
}
})
}
fw.Stop()
}
func TestExtractSubResourcesConfigDump(t *testing.T) {
input, err := readInputConfig("in.all.json")
assert.NoError(t, err)
fw, err := newFakePortForwarder(input)
assert.NoError(t, err)
err = fw.Start()
assert.NoError(t, err)
cases := []struct {
output string
expected string
resourceType envoyConfigType
}{
{
output: "json",
resourceType: BootstrapEnvoyConfigType,
expected: "out.bootstrap.json",
},
{
output: "yaml",
resourceType: BootstrapEnvoyConfigType,
expected: "out.bootstrap.yaml",
}, {
output: "json",
resourceType: ClusterEnvoyConfigType,
expected: "out.cluster.json",
},
{
output: "yaml",
resourceType: ClusterEnvoyConfigType,
expected: "out.cluster.yaml",
}, {
output: "json",
resourceType: ListenerEnvoyConfigType,
expected: "out.listener.json",
},
{
output: "yaml",
resourceType: ListenerEnvoyConfigType,
expected: "out.listener.yaml",
}, {
output: "json",
resourceType: RouteEnvoyConfigType,
expected: "out.route.json",
},
{
output: "yaml",
resourceType: RouteEnvoyConfigType,
expected: "out.route.yaml",
},
{
output: "json",
resourceType: EndpointEnvoyConfigType,
expected: "out.endpoints.json",
},
{
output: "yaml",
resourceType: EndpointEnvoyConfigType,
expected: "out.endpoints.yaml",
},
}
for _, tc := range cases {
t.Run(tc.output, func(t *testing.T) {
configDump, err := fetchGatewayConfig(fw, false)
assert.NoError(t, err)
resource, err := GetXDSResource(tc.resourceType, configDump)
assert.NoError(t, err)
got, err := formatGatewayConfig(resource, tc.output)
assert.NoError(t, err)
out, err := readOutputConfig(tc.expected)
assert.NoError(t, err)
if tc.output == "yaml" {
assert.YAMLEq(t, string(out), string(got))
} else {
assert.JSONEq(t, string(out), string(got))
}
})
}
fw.Stop()
}
func readInputConfig(filename string) ([]byte, error) {
b, err := os.ReadFile(path.Join("testdata", "config", "input", filename))
if err != nil {
return nil, err
}
return b, nil
}
func readOutputConfig(filename string) ([]byte, error) {
b, err := os.ReadFile(path.Join("testdata", "config", "output", filename))
if err != nil {
return nil, err
}
return b, nil
}

View File

@@ -0,0 +1,172 @@
// 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 kubernetes
import (
"bytes"
"context"
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
kubescheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/remotecommand"
)
type CLIClient interface {
// RESTConfig returns the Kubernetes rest.Config used to configure the clients.
RESTConfig() *rest.Config
// Pod returns the pod for the given namespaced name.
Pod(namespacedName types.NamespacedName) (*corev1.Pod, error)
// PodsForSelector finds pods matching selector.
PodsForSelector(namespace string, labelSelectors ...string) (*corev1.PodList, error)
// PodExec takes a command and the pod data to run the command in the specified pod.
PodExec(namespacedName types.NamespacedName, container string, command string) (stdout string, stderr string, err error)
}
var _ CLIClient = &client{}
type client struct {
config *rest.Config
restClient *rest.RESTClient
kube kubernetes.Interface
}
func NewCLIClient(clientConfig clientcmd.ClientConfig) (CLIClient, error) {
return newClientInternal(clientConfig)
}
func newClientInternal(clientConfig clientcmd.ClientConfig) (*client, error) {
var (
c client
err error
)
c.config, err = clientConfig.ClientConfig()
if err != nil {
return nil, err
}
setRestDefaults(c.config)
c.restClient, err = rest.RESTClientFor(c.config)
if err != nil {
return nil, err
}
c.kube, err = kubernetes.NewForConfig(c.config)
if err != nil {
return nil, err
}
return &c, err
}
func setRestDefaults(config *rest.Config) *rest.Config {
if config.GroupVersion == nil || config.GroupVersion.Empty() {
config.GroupVersion = &corev1.SchemeGroupVersion
}
if len(config.APIPath) == 0 {
if len(config.GroupVersion.Group) == 0 {
config.APIPath = "/api"
} else {
config.APIPath = "/apis"
}
}
if len(config.ContentType) == 0 {
config.ContentType = runtime.ContentTypeJSON
}
if config.NegotiatedSerializer == nil {
// This codec factory ensures the resources are not converted. Therefore, resources
// will not be round-tripped through internal versions. Defaulting does not happen
// on the client.
config.NegotiatedSerializer = serializer.NewCodecFactory(kubescheme.Scheme).WithoutConversion()
}
return config
}
func (c *client) RESTConfig() *rest.Config {
if c.config == nil {
return nil
}
cpy := *c.config
return &cpy
}
func (c *client) PodsForSelector(namespace string, podSelectors ...string) (*corev1.PodList, error) {
return c.kube.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: strings.Join(podSelectors, ","),
})
}
func (c *client) Pod(namespacedName types.NamespacedName) (*corev1.Pod, error) {
return c.kube.CoreV1().Pods(namespacedName.Namespace).Get(context.TODO(), namespacedName.Name, metav1.GetOptions{})
}
func (c *client) PodExec(namespacedName types.NamespacedName, container string, command string) (stdout string, stderr string, err error) {
defer func() {
if err != nil {
if len(stderr) > 0 {
err = fmt.Errorf("error exec into %s/%s container %s: %v\n%s",
namespacedName.Namespace, namespacedName.Name, container, err, stderr)
} else {
err = fmt.Errorf("error exec into %s/%s container %s: %v",
namespacedName.Namespace, namespacedName.Name, container, err)
}
}
}()
req := c.restClient.Post().
Resource("pods").
Namespace(namespacedName.Namespace).
Name(namespacedName.Name).
SubResource("exec").
Param("container", container).
VersionedParams(&corev1.PodExecOptions{
Container: container,
Command: strings.Fields(command),
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, kubescheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(c.config, "POST", req.URL())
if err != nil {
return "", "", err
}
var stdoutBuf, stderrBuf bytes.Buffer
err = exec.Stream(remotecommand.StreamOptions{
Stdin: nil,
Stdout: &stdoutBuf,
Stderr: &stderrBuf,
Tty: false,
})
stdout = stdoutBuf.String()
stderr = stderrBuf.String()
return
}

View File

@@ -0,0 +1,155 @@
// 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 kubernetes
import (
"fmt"
"io"
"net"
"net/http"
"os"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
)
const (
DefaultLocalAddress = "localhost"
)
func LocalAvailablePort() (int, error) {
l, err := net.Listen("tcp", fmt.Sprintf("%s:0", DefaultLocalAddress))
if err != nil {
return 0, err
}
return l.Addr().(*net.TCPAddr).Port, l.Close()
}
type PortForwarder interface {
Start() error
Stop()
// Address returns the address of the local forwarded address.
Address() string
}
var _ PortForwarder = &localForwarder{}
type localForwarder struct {
types.NamespacedName
CLIClient
localPort int
podPort int
stopCh chan struct{}
}
func NewLocalPortForwarder(client CLIClient, namespacedName types.NamespacedName, localPort, podPort int) (PortForwarder, error) {
f := &localForwarder{
stopCh: make(chan struct{}),
CLIClient: client,
NamespacedName: namespacedName,
localPort: localPort,
podPort: podPort,
}
if f.localPort == 0 {
// get a random port
p, err := LocalAvailablePort()
if err != nil {
return nil, errors.Wrapf(err, "failed to get a local available port")
}
f.localPort = p
}
return f, nil
}
func (f *localForwarder) Start() error {
errCh := make(chan error, 1)
readyCh := make(chan struct{}, 1)
go func() {
for {
select {
case <-f.stopCh:
return
default:
}
fw, err := f.buildKubernetesPortForwarder(readyCh)
if err != nil {
errCh <- err
return
}
if err := fw.ForwardPorts(); err != nil {
errCh <- err
return
}
readyCh = nil
}
}()
select {
case err := <-errCh:
return errors.Wrap(err, "failed to start port forwarder")
case <-readyCh:
return nil
}
}
func (f *localForwarder) buildKubernetesPortForwarder(readyCh chan struct{}) (*portforward.PortForwarder, error) {
restClient, err := rest.RESTClientFor(f.RESTConfig())
if err != nil {
return nil, err
}
req := restClient.Post().Resource("pods").Namespace(f.Namespace).Name(f.Name).SubResource("portforward")
serverURL := req.URL()
roundTripper, upgrader, err := spdy.RoundTripperFor(f.RESTConfig())
if err != nil {
return nil, fmt.Errorf("failure creating roundtripper: %v", err)
}
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, serverURL)
fw, err := portforward.NewOnAddresses(dialer,
[]string{DefaultLocalAddress},
[]string{fmt.Sprintf("%d:%d", f.localPort, f.podPort)},
f.stopCh,
readyCh,
io.Discard,
os.Stderr)
if err != nil {
return nil, fmt.Errorf("failed establishing portforward: %v", err)
}
return fw, nil
}
func (f *localForwarder) Stop() {
close(f.stopCh)
}
func (f *localForwarder) Address() string {
return fmt.Sprintf("%s:%d", DefaultLocalAddress, f.localPort)
}

33
pkg/cmd/hgctl/root.go Normal file
View File

@@ -0,0 +1,33 @@
// 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 hgctl
import "github.com/spf13/cobra"
// GetRootCommand returns the root cobra command to be executed
// by hgctl main.
func GetRootCommand() *cobra.Command {
rootCmd := &cobra.Command{
Use: "hgctl",
Long: "A command line utility for operating Higress",
SilenceUsage: true,
DisableAutoGenTag: true,
}
rootCmd.AddCommand(newVersionCommand())
rootCmd.AddCommand(newConfigCommand())
return rootCmd
}

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
{
"@type": "type.googleapis.com/envoy.admin.v3.ClustersConfigDump",
"version_info": "2",
"static_clusters": [{
"cluster": {
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "xds_cluster",
"type": "STRICT_DNS",
"connect_timeout": "1s",
"transport_socket": {
"name": "envoy.transport_sockets.tls",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"common_tls_context": {
"tls_params": {
"tls_maximum_protocol_version": "TLSv1_3"
},
"tls_certificate_sds_secret_configs": [{
"name": "xds_certificate",
"sds_config": {
"resource_api_version": "V3",
"path_config_source": {
"path": "/sds/xds-certificate.json"
}
}
}],
"validation_context_sds_secret_config": {
"name": "xds_trusted_ca",
"sds_config": {
"resource_api_version": "V3",
"path_config_source": {
"path": "/sds/xds-trusted-ca.json"
}
}
}
}
}
},
"load_assignment": {
"cluster_name": "xds_cluster",
"endpoints": [{
"lb_endpoints": [{
"endpoint": {
"address": {
"socket_address": {
"address": "higress",
"port_value": 18000
}
}
}
}]
}]
},
"typed_extension_protocol_options": {
"envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
"@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
"explicit_http_config": {
"http2_protocol_options": {}
}
}
}
},
"last_updated": "2023-02-23T09:05:23.436Z"
}],
"dynamic_active_clusters": [{
"version_info": "2a0a1698a9d3e05b802047b0cd36b52a070afa49042e1ba267168c5265c7cabf",
"cluster": {
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "default-backend-rule-0-match-0-www.example.com",
"type": "STATIC",
"connect_timeout": "5s",
"dns_lookup_family": "V4_ONLY",
"outlier_detection": {},
"common_lb_config": {
"locality_weighted_lb_config": {}
},
"load_assignment": {
"cluster_name": "default-backend-rule-0-match-0-www.example.com",
"endpoints": [{
"locality": {},
"lb_endpoints": [{
"endpoint": {
"address": {
"socket_address": {
"address": "0.0.0.0",
"port_value": 3000
}
}
},
"load_balancing_weight": 1
}],
"load_balancing_weight": 1
}]
}
},
"last_updated": "2023-02-23T09:05:38.443Z"
}]
}

View File

@@ -0,0 +1,67 @@
---
"@type": type.googleapis.com/envoy.admin.v3.ClustersConfigDump
version_info: '2'
static_clusters:
- cluster:
"@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: xds_cluster
type: STRICT_DNS
connect_timeout: 1s
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
common_tls_context:
tls_params:
tls_maximum_protocol_version: TLSv1_3
tls_certificate_sds_secret_configs:
- name: xds_certificate
sds_config:
resource_api_version: V3
path_config_source:
path: "/sds/xds-certificate.json"
validation_context_sds_secret_config:
name: xds_trusted_ca
sds_config:
resource_api_version: V3
path_config_source:
path: "/sds/xds-trusted-ca.json"
load_assignment:
cluster_name: xds_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: higress
port_value: 18000
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
last_updated: '2023-02-23T09:05:23.436Z'
dynamic_active_clusters:
- version_info: 2a0a1698a9d3e05b802047b0cd36b52a070afa49042e1ba267168c5265c7cabf
cluster:
"@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: default-backend-rule-0-match-0-www.example.com
type: STATIC
connect_timeout: 5s
dns_lookup_family: V4_ONLY
outlier_detection: {}
common_lb_config:
locality_weighted_lb_config: {}
load_assignment:
cluster_name: default-backend-rule-0-match-0-www.example.com
endpoints:
- locality: {}
lb_endpoints:
- endpoint:
address:
socket_address:
address: 0.0.0.0
port_value: 3000
load_balancing_weight: 1
load_balancing_weight: 1
last_updated: '2023-02-23T09:05:38.443Z'

View File

@@ -0,0 +1,30 @@
{
"@type": "type.googleapis.com/envoy.admin.v3.EndpointsConfigDump",
"staticEndpointConfigs": [{
"endpointConfig": {
"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"clusterName": "xds_cluster",
"endpoints": [{
"locality": {},
"lbEndpoints": [{
"endpoint": {
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 18000
}
},
"healthCheckConfig": {},
"hostname": "higress"
},
"healthStatus": "HEALTHY",
"metadata": {},
"loadBalancingWeight": 1
}]
}],
"policy": {
"overprovisioningFactor": 140
}
}
}]
}

View File

@@ -0,0 +1,21 @@
---
"@type": type.googleapis.com/envoy.admin.v3.EndpointsConfigDump
staticEndpointConfigs:
- endpointConfig:
"@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment
clusterName: xds_cluster
endpoints:
- locality: {}
lbEndpoints:
- endpoint:
address:
socketAddress:
address: 0.0.0.0
portValue: 18000
healthCheckConfig: {}
hostname: higress
healthStatus: HEALTHY
metadata: {}
loadBalancingWeight: 1
policy:
overprovisioningFactor: 140

View File

@@ -0,0 +1,77 @@
{
"@type": "type.googleapis.com/envoy.admin.v3.ListenersConfigDump",
"version_info": "2",
"dynamic_listeners": [{
"name": "default-higress-http",
"active_state": {
"version_info": "42c71fb50c315ee3a32b327da69f8cc0baf420bc84b747e82d9c38e1b0c33eb2",
"listener": {
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "default-higress-http",
"address": {
"socket_address": {
"address": "0.0.0.0",
"port_value": 10080
}
},
"access_log": [{
"name": "envoy.access_loggers.file",
"filter": {
"response_flag_filter": {
"flags": [
"NR"
]
}
},
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog",
"path": "/dev/stdout"
}
}],
"default_filter_chain": {
"filters": [{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"stat_prefix": "http",
"rds": {
"config_source": {
"api_config_source": {
"api_type": "DELTA_GRPC",
"grpc_services": [{
"envoy_grpc": {
"cluster_name": "xds_cluster"
}
}],
"set_node_on_first_message_only": true,
"transport_api_version": "V3"
},
"resource_api_version": "V3"
},
"route_config_name": "default-higress-http"
},
"http_filters": [{
"name": "envoy.filters.http.router",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
}
}],
"access_log": [{
"name": "envoy.access_loggers.file",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog",
"path": "/dev/stdout"
}
}],
"use_remote_address": true,
"upgrade_configs": [{
"upgrade_type": "websocket"
}]
}
}]
}
},
"last_updated": "2023-02-23T09:05:38.446Z"
}
}]
}

View File

@@ -0,0 +1,53 @@
---
"@type": type.googleapis.com/envoy.admin.v3.ListenersConfigDump
version_info: '2'
dynamic_listeners:
- name: default-higress-http
active_state:
version_info: 42c71fb50c315ee3a32b327da69f8cc0baf420bc84b747e82d9c38e1b0c33eb2
listener:
"@type": type.googleapis.com/envoy.config.listener.v3.Listener
name: default-higress-http
address:
socket_address:
address: 0.0.0.0
port_value: 10080
access_log:
- name: envoy.access_loggers.file
filter:
response_flag_filter:
flags:
- NR
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: "/dev/stdout"
default_filter_chain:
filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: http
rds:
config_source:
api_config_source:
api_type: DELTA_GRPC
grpc_services:
- envoy_grpc:
cluster_name: xds_cluster
set_node_on_first_message_only: true
transport_api_version: V3
resource_api_version: V3
route_config_name: default-higress-http
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
access_log:
- name: envoy.access_loggers.file
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: "/dev/stdout"
use_remote_address: true
upgrade_configs:
- upgrade_type: websocket
last_updated: '2023-02-23T09:05:38.446Z'

View File

@@ -0,0 +1,31 @@
{
"@type": "type.googleapis.com/envoy.admin.v3.RoutesConfigDump",
"dynamic_route_configs": [{
"version_info": "cb1e51997a9c3aa6f4d920f39fd5bdbd966e9382b7b6bdf42efca8c22c6c3442",
"route_config": {
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "default-higress-http",
"virtual_hosts": [{
"name": "default-higress-http",
"domains": [
"*"
],
"routes": [{
"match": {
"prefix": "/",
"headers": [{
"name": ":authority",
"string_match": {
"exact": "www.example.com"
}
}]
},
"route": {
"cluster": "default-backend-rule-0-match-0-www.example.com"
}
}]
}]
},
"last_updated": "2023-02-23T09:05:38.448Z"
}]
}

View File

@@ -0,0 +1,21 @@
---
"@type": type.googleapis.com/envoy.admin.v3.RoutesConfigDump
dynamic_route_configs:
- version_info: cb1e51997a9c3aa6f4d920f39fd5bdbd966e9382b7b6bdf42efca8c22c6c3442
route_config:
"@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration
name: default-higress-http
virtual_hosts:
- name: default-higress-http
domains:
- "*"
routes:
- match:
prefix: "/"
headers:
- name: ":authority"
string_match:
exact: www.example.com
route:
cluster: default-backend-rule-0-match-0-www.example.com
last_updated: '2023-02-23T09:05:38.448Z'

81
pkg/cmd/hgctl/utils.go Normal file
View File

@@ -0,0 +1,81 @@
// 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 hgctl
import (
"encoding/json"
"fmt"
)
type envoyConfigType string
var (
BootstrapEnvoyConfigType envoyConfigType = "bootstrap"
ClusterEnvoyConfigType envoyConfigType = "cluster"
EndpointEnvoyConfigType envoyConfigType = "endpoint"
ListenerEnvoyConfigType envoyConfigType = "listener"
RouteEnvoyConfigType envoyConfigType = "route"
AllEnvoyConfigType envoyConfigType = "all"
)
func GetXDSResource(resourceType envoyConfigType, configDump []byte) (any, error) {
cd := map[string]any{}
if err := json.Unmarshal(configDump, &cd); err != nil {
return nil, err
}
if resourceType == AllEnvoyConfigType {
return cd, nil
}
configs := cd["configs"]
globalConfigs := configs.([]any)
switch resourceType {
case BootstrapEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump" {
return config, nil
}
}
case EndpointEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.EndpointsConfigDump" {
return config, nil
}
}
case ClusterEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.ClustersConfigDump" {
return config, nil
}
}
case ListenerEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.ListenersConfigDump" {
return config, nil
}
}
case RouteEnvoyConfigType:
for _, config := range globalConfigs {
if config.(map[string]interface{})["@type"] == "type.googleapis.com/envoy.admin.v3.RoutesConfigDump" {
return config, nil
}
}
default:
return nil, fmt.Errorf("unknown resourceType %s", resourceType)
}
return nil, fmt.Errorf("unknown resourceType %s", resourceType)
}

193
pkg/cmd/hgctl/version.go Normal file
View File

@@ -0,0 +1,193 @@
// 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 hgctl
import (
"encoding/json"
"fmt"
"io"
"sort"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/alibaba/higress/pkg/cmd/version"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
const (
yamlOutput = "yaml"
jsonOutput = "json"
higressCoreContainerName = "higress-core"
higressGatewayContainerName = "higress-gateway"
)
func newVersionCommand() *cobra.Command {
var (
output string
client bool
)
versionCommand := &cobra.Command{
Use: "version",
Aliases: []string{"versions", "v"},
Short: "Show version",
Example: ` # Show versions of both client and server.
hgctl version
# Show versions of both client and server in JSON format.
hgctl version --output=json
# Show version of client without server.
hgctl version --client
`,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(versions(cmd.OutOrStdout(), output, client))
},
}
flags := versionCommand.Flags()
options.AddKubeConfigFlags(flags)
versionCommand.PersistentFlags().StringVarP(&output, "output", "o", yamlOutput, "One of 'yaml' or 'json'")
versionCommand.PersistentFlags().BoolVarP(&client, "client", "r", false, "If true, only log client version.")
return versionCommand
}
type VersionInfo struct {
ClientVersion string `json:"client" yaml:"client"`
ServerVersions []*ServerVersion `json:"server,omitempty" yaml:"server"`
}
type ServerVersion struct {
types.NamespacedName `yaml:"namespacedName"`
version.Info `yaml:"versionInfo"`
}
func Get() VersionInfo {
return VersionInfo{
ClientVersion: version.Get().HigressVersion,
ServerVersions: make([]*ServerVersion, 0),
}
}
func retrieveVersion(w io.Writer, v *VersionInfo, containerName string, cmd string, labelSelector string, c kubernetes.CLIClient, f versionFunc) error {
pods, err := c.PodsForSelector(metav1.NamespaceAll, labelSelector)
if err != nil {
return errors.Wrap(err, "list Higress pods failed")
}
for _, pod := range pods.Items {
if pod.Status.Phase != v1.PodRunning {
fmt.Fprintf(w, "WARN: pod %s/%s is not running, skipping it.", pod.Namespace, pod.Name)
continue
}
nn := types.NamespacedName{
Namespace: pod.Namespace,
Name: pod.Name,
}
stdout, _, err := c.PodExec(nn, containerName, cmd)
if err != nil {
return fmt.Errorf("pod exec on %s/%s failed: %w", nn.Namespace, nn.Name, err)
}
info, err := f(stdout)
if err != nil {
return err
}
v.ServerVersions = append(v.ServerVersions, &ServerVersion{
NamespacedName: nn,
Info: *info,
})
}
return nil
}
type versionFunc func(string) (*version.Info, error)
func versions(w io.Writer, output string, client bool) error {
v := Get()
if client {
fmt.Fprintf(w, "clientVersion: %s", v.ClientVersion)
return nil
}
c, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return fmt.Errorf("failed to build kubernetes client: %w", err)
}
if err := retrieveVersion(w, &v, higressCoreContainerName, "higress version -ojson", "app=higress-controller", c, func(s string) (*version.Info, error) {
info := &version.Info{}
if err := json.Unmarshal([]byte(s), info); err != nil {
return nil, fmt.Errorf("unmarshall pod exec result failed: %w", err)
}
info.Type = "higress-controller"
return info, nil
}); err != nil {
return err
}
if err := retrieveVersion(w, &v, higressGatewayContainerName, "envoy --version", "app=higress-gateway", c, func(s string) (*version.Info, error) {
if len(strings.Split(s, ":")) != 2 {
return nil, nil
}
proxyVersion := strings.TrimSpace(strings.Split(s, ":")[1])
return &version.Info{
GatewayVersion: proxyVersion,
Type: "higress-gateway",
}, nil
}); err != nil {
return err
}
sort.Slice(v.ServerVersions, func(i, j int) bool {
if v.ServerVersions[i].Namespace == v.ServerVersions[j].Namespace {
return v.ServerVersions[i].Name < v.ServerVersions[j].Name
}
return v.ServerVersions[i].Namespace < v.ServerVersions[j].Namespace
})
var out []byte
switch output {
case yamlOutput:
out, err = yaml.Marshal(v)
case jsonOutput:
out, err = json.MarshalIndent(v, "", " ")
default:
out, err = json.MarshalIndent(v, "", " ")
}
if err != nil {
return err
}
fmt.Fprintln(w, string(out))
return nil
}

29
pkg/cmd/options/global.go Normal file
View File

@@ -0,0 +1,29 @@
// 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 options
import (
"github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
var DefaultConfigFlags = genericclioptions.NewConfigFlags(true)
func AddKubeConfigFlags(flags *pflag.FlagSet) {
flags.StringVar(DefaultConfigFlags.KubeConfig, "kubeconfig", *DefaultConfigFlags.KubeConfig,
"Path to the kubeconfig file to use for CLI requests.")
flags.StringVar(DefaultConfigFlags.Context, "context", *DefaultConfigFlags.Context,
"The name of the kubeconfig context to use.")
}

32
pkg/cmd/root.go Normal file
View File

@@ -0,0 +1,32 @@
// 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 cmd
import "github.com/spf13/cobra"
// GetRootCommand returns the root cobra command to be executed
// by main.
func GetRootCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "higress",
Short: "Higress",
Long: "Next-generation Cloud Native Gateway",
}
cmd.AddCommand(getServerCommand())
cmd.AddCommand(getVersionCommand())
return cmd
}

120
pkg/cmd/server.go Normal file
View File

@@ -0,0 +1,120 @@
// 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 cmd
import (
"fmt"
"time"
"github.com/alibaba/higress/pkg/bootstrap"
innerconstants "github.com/alibaba/higress/pkg/config/constants"
"github.com/spf13/cobra"
"istio.io/istio/pilot/pkg/features"
"istio.io/istio/pkg/cmd"
"istio.io/istio/pkg/config/constants"
"istio.io/istio/pkg/keepalive"
"istio.io/pkg/log"
)
var (
serverArgs *bootstrap.ServerArgs
loggingOptions = log.DefaultOptions()
serverProvider = func(args *bootstrap.ServerArgs) (bootstrap.ServerInterface, error) {
return bootstrap.NewServer(serverArgs)
}
waitForMonitorSignal = func(stop chan struct{}) {
cmd.WaitSignal(stop)
}
)
// getServerCommand returns the server cobra command to be executed.
func getServerCommand() *cobra.Command {
serveCmd := &cobra.Command{
Use: "serve",
Aliases: []string{"serve"},
Short: "Starts the higress ingress controller",
Example: "higress serve",
PreRunE: func(c *cobra.Command, args []string) error {
return log.Configure(loggingOptions)
},
RunE: func(c *cobra.Command, args []string) error {
cmd.PrintFlags(c.Flags())
stop := make(chan struct{})
server, err := serverProvider(serverArgs)
if err != nil {
return fmt.Errorf("fail to create higress server: %v", err)
}
if err := server.Start(stop); err != nil {
return fmt.Errorf("fail to start higress server: %v", err)
}
waitForMonitorSignal(stop)
server.WaitUntilCompletion()
return nil
},
}
serverArgs = &bootstrap.ServerArgs{
Debug: true,
NativeIstio: true,
HttpAddress: ":8888",
GrpcAddress: ":15051",
GrpcKeepAliveOptions: keepalive.DefaultOption(),
XdsOptions: bootstrap.XdsOptions{
DebounceAfter: features.DebounceAfter,
DebounceMax: features.DebounceMax,
EnableEDSDebounce: features.EnableEDSDebounce,
},
}
serveCmd.PersistentFlags().StringVar(&serverArgs.GatewaySelectorKey, "gatewaySelectorKey", "higress", "gateway resource selector label key")
serveCmd.PersistentFlags().StringVar(&serverArgs.GatewaySelectorValue, "gatewaySelectorValue", "higress-gateway", "gateway resource selector label value")
serveCmd.PersistentFlags().BoolVar(&serverArgs.EnableStatus, "enableStatus", true, "enable the ingress status syncer which use to update the ip in ingress's status")
serveCmd.PersistentFlags().StringVar(&serverArgs.IngressClass, "ingressClass", innerconstants.DefaultIngressClass, "if not empty, only watch the ingresses have the specified class, otherwise watch all ingresses")
serveCmd.PersistentFlags().StringVar(&serverArgs.WatchNamespace, "watchNamespace", "", "if not empty, only wath the ingresses in the specified namespace, otherwise watch in all namespacees")
serveCmd.PersistentFlags().BoolVar(&serverArgs.Debug, "debug", serverArgs.Debug, "if true, enables more debug http api")
serveCmd.PersistentFlags().StringVar(&serverArgs.HttpAddress, "httpAddress", serverArgs.HttpAddress, "the http address")
serveCmd.PersistentFlags().StringVar(&serverArgs.GrpcAddress, "grpcAddress", serverArgs.GrpcAddress, "the grpc address")
serveCmd.PersistentFlags().BoolVar(&serverArgs.KeepStaleWhenEmpty, "keepStaleWhenEmpty", false, "keep the stale service entry when there are no endpoints in the service")
serveCmd.PersistentFlags().StringVar(&serverArgs.RegistryOptions.ClusterRegistriesNamespace, "clusterRegistriesNamespace",
serverArgs.RegistryOptions.ClusterRegistriesNamespace, "Namespace for ConfigMap which stores clusters configs")
serveCmd.PersistentFlags().StringVar(&serverArgs.RegistryOptions.KubeConfig, "kubeconfig", "",
"Use a Kubernetes configuration file instead of in-cluster configuration")
// RegistryOptions Controller options
serveCmd.PersistentFlags().DurationVar(&serverArgs.RegistryOptions.KubeOptions.ResyncPeriod, "resync", 60*time.Second,
"Controller resync interval")
serveCmd.PersistentFlags().StringVar(&serverArgs.RegistryOptions.KubeOptions.DomainSuffix, "domain", constants.DefaultKubernetesDomain,
"DNS domain suffix")
serveCmd.PersistentFlags().StringVar((*string)(&serverArgs.RegistryOptions.KubeOptions.ClusterID), "clusterID", "Kubernetes",
"The ID of the cluster that this instance resides")
serveCmd.PersistentFlags().StringToStringVar(&serverArgs.RegistryOptions.KubeOptions.ClusterAliases, "clusterAliases", map[string]string{},
"Alias names for clusters")
serveCmd.PersistentFlags().Float32Var(&serverArgs.RegistryOptions.KubeOptions.KubernetesAPIQPS, "kubernetesApiQPS", 80.0,
"Maximum QPS when communicating with the kubernetes API")
serveCmd.PersistentFlags().IntVar(&serverArgs.RegistryOptions.KubeOptions.KubernetesAPIBurst, "kubernetesApiBurst", 160,
"Maximum burst for throttle when communicating with the kubernetes API")
loggingOptions.AttachCobraFlags(serveCmd)
serverArgs.GrpcKeepAliveOptions.AttachCobraFlags(serveCmd)
return serveCmd
}

View File

@@ -12,18 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package main
package cmd
import (
"github.com/alibaba/higress/pkg/bootstrap"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"os"
"testing"
"time"
"github.com/alibaba/higress/pkg/bootstrap"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)
func TestServe(t *testing.T) {
serveCmd := getServerCommand()
runEBackup := serveCmd.RunE
argsBackup := os.Args
serverProviderBackup := serverProvider
@@ -53,7 +55,9 @@ func TestServe(t *testing.T) {
time.Sleep(delay)
close(stop)
}
main()
serveCmd.Execute()
end := time.Now()
cost := end.Sub(start)

38
pkg/cmd/version.go Normal file
View File

@@ -0,0 +1,38 @@
// 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 cmd
import (
"github.com/alibaba/higress/pkg/cmd/version"
"github.com/spf13/cobra"
)
// getVersionCommand returns the version cobra command to be executed.
func getVersionCommand() *cobra.Command {
var output string
cmd := &cobra.Command{
Use: "version",
Aliases: []string{"versions", "v"},
Short: "Show versions",
RunE: func(cmd *cobra.Command, args []string) error {
return version.Print(cmd.OutOrStdout(), output)
},
}
cmd.PersistentFlags().StringVarP(&output, "output", "o", "", "One of 'yaml' or 'json'")
return cmd
}

View File

@@ -0,0 +1,62 @@
// 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 version
import (
"encoding/json"
"fmt"
"io"
"sigs.k8s.io/yaml"
)
type Info struct {
Type string `json:"type,omitempty" yaml:"type,omitempty"`
HigressVersion string `json:"higressVersion,omitempty" yaml:"higressVersion,omitempty"`
GitCommitID string `json:"gitCommitID,omitempty" yaml:"gitCommitID,omitempty"`
GatewayVersion string `json:"gatewayVersion,omitempty" yaml:"gatewayVersion,omitempty"`
}
func Get() Info {
return Info{
HigressVersion: higressVersion,
GitCommitID: gitCommitID,
}
}
var (
higressVersion string
gitCommitID string
)
// Print shows the versions of the Envoy Gateway.
func Print(w io.Writer, format string) error {
v := Get()
switch format {
case "json":
if marshalled, err := json.MarshalIndent(v, "", " "); err == nil {
_, _ = fmt.Fprintln(w, string(marshalled))
}
case "yaml":
if marshalled, err := yaml.Marshal(v); err == nil {
_, _ = fmt.Fprintln(w, string(marshalled))
}
default:
_, _ = fmt.Fprintf(w, "HIGRESS_VERSION: %s\n", v.HigressVersion)
_, _ = fmt.Fprintf(w, "GIT_COMMIT_ID: %s\n", v.GitCommitID)
}
return nil
}

View File

@@ -559,27 +559,36 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [
IngressLog.Debugf("traffic policy number %d", len(convertOptions.Service2TrafficPolicy))
for _, wrapperTrafficPolicy := range convertOptions.Service2TrafficPolicy {
m.annotationHandler.ApplyTrafficPolicy(wrapperTrafficPolicy.TrafficPolicy, wrapperTrafficPolicy.WrapperConfig.AnnotationsConfig)
m.annotationHandler.ApplyTrafficPolicy(wrapperTrafficPolicy.TrafficPolicy, wrapperTrafficPolicy.PortTrafficPolicy, wrapperTrafficPolicy.WrapperConfig.AnnotationsConfig)
}
// Merge multi-port traffic policy per service into one destination rule.
destinationRules := map[string]*common.WrapperDestinationRule{}
for key, wrapperTrafficPolicy := range convertOptions.Service2TrafficPolicy {
serviceName := util.CreateServiceFQDN(key.Namespace, key.Name)
var serviceName string
if key.ServiceFQDN != "" {
serviceName = key.ServiceFQDN
} else {
serviceName = util.CreateServiceFQDN(key.Namespace, key.Name)
}
dr, exist := destinationRules[serviceName]
if !exist {
trafficPolicy := &networking.TrafficPolicy{}
if wrapperTrafficPolicy.PortTrafficPolicy != nil {
trafficPolicy.PortLevelSettings = []*networking.TrafficPolicy_PortTrafficPolicy{wrapperTrafficPolicy.PortTrafficPolicy}
} else if wrapperTrafficPolicy.TrafficPolicy != nil {
trafficPolicy = wrapperTrafficPolicy.TrafficPolicy
}
dr = &common.WrapperDestinationRule{
DestinationRule: &networking.DestinationRule{
Host: serviceName,
TrafficPolicy: &networking.TrafficPolicy{
PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{wrapperTrafficPolicy.TrafficPolicy},
},
Host: serviceName,
TrafficPolicy: trafficPolicy,
},
WrapperConfig: wrapperTrafficPolicy.WrapperConfig,
ServiceKey: key,
}
} else {
dr.DestinationRule.TrafficPolicy.PortLevelSettings = append(dr.DestinationRule.TrafficPolicy.PortLevelSettings, wrapperTrafficPolicy.TrafficPolicy)
} else if wrapperTrafficPolicy.PortTrafficPolicy != nil {
dr.DestinationRule.TrafficPolicy.PortLevelSettings = append(dr.DestinationRule.TrafficPolicy.PortLevelSettings, wrapperTrafficPolicy.PortTrafficPolicy)
}
destinationRules[serviceName] = dr
@@ -707,7 +716,10 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
if result.PluginConfig != nil {
return result, nil
}
result.PluginConfig = obj.DefaultConfig
if !obj.DefaultConfigDisable {
result.PluginConfig = obj.DefaultConfig
}
hasValidRule := false
if len(obj.MatchRules) > 0 {
if result.PluginConfig == nil {
result.PluginConfig = &types.Struct{
@@ -716,6 +728,9 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
}
var ruleValues []*types.Value
for _, rule := range obj.MatchRules {
if rule.ConfigDisable {
continue
}
if rule.Config == nil {
return nil, errors.New("invalid rule has no config")
}
@@ -764,14 +779,20 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
Kind: v,
})
}
result.PluginConfig.Fields["_rules_"] = &types.Value{
Kind: &types.Value_ListValue{
ListValue: &types.ListValue{
Values: ruleValues,
if len(ruleValues) > 0 {
hasValidRule = true
result.PluginConfig.Fields["_rules_"] = &types.Value{
Kind: &types.Value_ListValue{
ListValue: &types.ListValue{
Values: ruleValues,
},
},
},
}
}
}
if !hasValidRule && obj.DefaultConfigDisable {
return nil, nil
}
return result, nil
}
@@ -802,6 +823,14 @@ func (m *IngressConfig) AddOrUpdateWasmPlugin(clusterNamespacedName util.Cluster
IngressLog.Errorf("invalid wasmPlugin:%s, err:%v", clusterNamespacedName.Name, err)
return
}
if istioWasmPlugin == nil {
IngressLog.Infof("wasmPlugin:%s will not be transferred to istio since config disabled",
clusterNamespacedName.Name)
m.mutex.Lock()
delete(m.wasmPlugins, clusterNamespacedName.Name)
m.mutex.Unlock()
return
}
IngressLog.Debugf("wasmPlugin:%s convert to istioWasmPlugin:%v", clusterNamespacedName.Name, istioWasmPlugin)
m.mutex.Lock()
m.wasmPlugins[clusterNamespacedName.Name] = istioWasmPlugin

View File

@@ -67,6 +67,8 @@ type Ingress struct {
IgnoreCase *IgnoreCaseConfig
Match *MatchConfig
HeaderControl *HeaderControlConfig
}
func (i *Ingress) NeedRegexMatch() bool {
@@ -138,6 +140,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
destination{},
ignoreCaseMatching{},
match{},
headerControl{},
},
gatewayHandlers: []GatewayHandler{
downstreamTLS{},
@@ -154,6 +157,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
fallback{},
ignoreCaseMatching{},
match{},
headerControl{},
},
trafficPolicyHandlers: []TrafficPolicyHandler{
upstreamTLS{},
@@ -188,8 +192,8 @@ func (h *AnnotationHandlerManager) ApplyRoute(route *networking.HTTPRoute, confi
}
}
func (h *AnnotationHandlerManager) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress) {
func (h *AnnotationHandlerManager) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy, portTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress) {
for _, handler := range h.trafficPolicyHandlers {
handler.ApplyTrafficPolicy(trafficPolicy, config)
handler.ApplyTrafficPolicy(trafficPolicy, portTrafficPolicy, config)
}
}

View File

@@ -135,6 +135,9 @@ func convertCredentials(secretType authSecretType, secret *corev1.Secret) ([]str
}
userList := strings.Split(string(users), "\n")
for _, item := range userList {
if !strings.Contains(item, ":") {
continue
}
result = append(result, item)
}
case authMapAuthSecretType:

View File

@@ -106,6 +106,8 @@ func ApplyByWeight(canary, route *networking.HTTPRoute, canaryIngress *Ingress)
// We will process total weight in the end.
route.Route = append(route.Route, canary.Route[0])
// canary route use the header control applied on itself.
headerControl{}.ApplyRoute(canary, canaryIngress)
// Move route level to destination level
canary.Route[0].Headers = canary.Headers
@@ -132,40 +134,48 @@ func ApplyByHeader(canary, route *networking.HTTPRoute, canaryIngress *Ingress)
// Modified match base on by header
if canaryConfig.Header != "" {
canary.Match[0].Headers = map[string]*networking.StringMatch{
canaryConfig.Header: {
MatchType: &networking.StringMatch_Exact{
Exact: "always",
},
},
}
if canaryConfig.HeaderValue != "" {
canary.Match[0].Headers = map[string]*networking.StringMatch{
for _, match := range canary.Match {
match.Headers = map[string]*networking.StringMatch{
canaryConfig.Header: {
MatchType: &networking.StringMatch_Regex{
Regex: "always|" + canaryConfig.HeaderValue,
MatchType: &networking.StringMatch_Exact{
Exact: "always",
},
},
}
} else if canaryConfig.HeaderPattern != "" {
canary.Match[0].Headers = map[string]*networking.StringMatch{
canaryConfig.Header: {
MatchType: &networking.StringMatch_Regex{
Regex: canaryConfig.HeaderPattern,
if canaryConfig.HeaderValue != "" {
match.Headers = map[string]*networking.StringMatch{
canaryConfig.Header: {
MatchType: &networking.StringMatch_Regex{
Regex: "always|" + canaryConfig.HeaderValue,
},
},
},
}
} else if canaryConfig.HeaderPattern != "" {
match.Headers = map[string]*networking.StringMatch{
canaryConfig.Header: {
MatchType: &networking.StringMatch_Regex{
Regex: canaryConfig.HeaderPattern,
},
},
}
}
}
} else if canaryConfig.Cookie != "" {
canary.Match[0].Headers = map[string]*networking.StringMatch{
"cookie": {
MatchType: &networking.StringMatch_Regex{
Regex: "^(.\\*?;)?(" + canaryConfig.Cookie + "=always)(;.\\*)?$",
for _, match := range canary.Match {
match.Headers = map[string]*networking.StringMatch{
"cookie": {
MatchType: &networking.StringMatch_Regex{
Regex: "^(.\\*?;)?(" + canaryConfig.Cookie + "=always)(;.\\*)?$",
},
},
},
}
}
}
canary.Headers = nil
// canary route use the header control applied on itself.
headerControl{}.ApplyRoute(canary, canaryIngress)
// First add normal route cluster
canary.Route[0].FallbackClusters = append(canary.Route[0].FallbackClusters,
route.Route[0].Destination.DeepCopy())

View File

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

View File

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

View File

@@ -0,0 +1,160 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotations
import (
"strings"
networking "istio.io/api/networking/v1alpha3"
. "github.com/alibaba/higress/pkg/ingress/log"
)
const (
// request
requestHeaderAdd = "request-header-control-add"
requestHeaderUpdate = "request-header-control-update"
requestHeaderRemove = "request-header-control-remove"
// response
responseHeaderAdd = "response-header-control-add"
responseHeaderUpdate = "response-header-control-update"
responseHeaderRemove = "response-header-control-remove"
)
var (
_ Parser = headerControl{}
_ RouteHandler = headerControl{}
)
type HeaderOperation struct {
Add map[string]string
Update map[string]string
Remove []string
}
// HeaderControlConfig enforces header operations on route level.
// Note: Canary route don't use header control applied on the normal route.
type HeaderControlConfig struct {
Request *HeaderOperation
Response *HeaderOperation
}
type headerControl struct{}
func (h headerControl) Parse(annotations Annotations, config *Ingress, _ *GlobalContext) error {
if !needHeaderControlConfig(annotations) {
return nil
}
config.HeaderControl = &HeaderControlConfig{}
var requestAdd map[string]string
var requestUpdate map[string]string
var requestRemove []string
if add, err := annotations.ParseStringForHigress(requestHeaderAdd); err == nil {
requestAdd = convertAddOrUpdate(add)
}
if update, err := annotations.ParseStringForHigress(requestHeaderUpdate); err == nil {
requestUpdate = convertAddOrUpdate(update)
}
if remove, err := annotations.ParseStringForHigress(requestHeaderRemove); err == nil {
requestRemove = splitBySeparator(remove, ",")
}
if len(requestAdd) > 0 || len(requestUpdate) > 0 || len(requestRemove) > 0 {
config.HeaderControl.Request = &HeaderOperation{
Add: requestAdd,
Update: requestUpdate,
Remove: requestRemove,
}
}
var responseAdd map[string]string
var responseUpdate map[string]string
var responseRemove []string
if add, err := annotations.ParseStringForHigress(responseHeaderAdd); err == nil {
responseAdd = convertAddOrUpdate(add)
}
if update, err := annotations.ParseStringForHigress(responseHeaderUpdate); err == nil {
responseUpdate = convertAddOrUpdate(update)
}
if remove, err := annotations.ParseStringForHigress(responseHeaderRemove); err == nil {
responseRemove = splitBySeparator(remove, ",")
}
if len(responseAdd) > 0 || len(responseUpdate) > 0 || len(responseRemove) > 0 {
config.HeaderControl.Response = &HeaderOperation{
Add: responseAdd,
Update: responseUpdate,
Remove: responseRemove,
}
}
return nil
}
func (h headerControl) ApplyRoute(route *networking.HTTPRoute, config *Ingress) {
headerControlConfig := config.HeaderControl
if headerControlConfig == nil {
return
}
headers := &networking.Headers{
Request: &networking.Headers_HeaderOperations{},
Response: &networking.Headers_HeaderOperations{},
}
if headerControlConfig.Request != nil {
headers.Request.Add = headerControlConfig.Request.Add
headers.Request.Set = headerControlConfig.Request.Update
headers.Request.Remove = headerControlConfig.Request.Remove
}
if headerControlConfig.Response != nil {
headers.Response.Add = headerControlConfig.Response.Add
headers.Response.Set = headerControlConfig.Response.Update
headers.Response.Remove = headerControlConfig.Response.Remove
}
route.Headers = headers
}
func needHeaderControlConfig(annotations Annotations) bool {
return annotations.HasHigress(requestHeaderAdd) ||
annotations.HasHigress(requestHeaderUpdate) ||
annotations.HasHigress(requestHeaderRemove) ||
annotations.HasHigress(responseHeaderAdd) ||
annotations.HasHigress(responseHeaderUpdate) ||
annotations.HasHigress(responseHeaderRemove)
}
func convertAddOrUpdate(headers string) map[string]string {
result := map[string]string{}
parts := strings.Split(headers, "\n")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
keyValue := strings.Fields(part)
if len(keyValue) != 2 {
IngressLog.Errorf("Header format %s is invalid.", keyValue)
continue
}
key := strings.TrimSpace(keyValue[0])
value := strings.TrimSpace(keyValue[1])
result[key] = value
}
return result
}

View File

@@ -0,0 +1,235 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotations
import (
"reflect"
"testing"
networking "istio.io/api/networking/v1alpha3"
)
func TestHeaderControlParse(t *testing.T) {
headerControl := &headerControl{}
inputCases := []struct {
input map[string]string
expect *HeaderControlConfig
}{
{},
{
input: map[string]string{
buildHigressAnnotationKey(requestHeaderAdd): "one 1",
buildHigressAnnotationKey(responseHeaderAdd): "A a",
},
expect: &HeaderControlConfig{
Request: &HeaderOperation{
Add: map[string]string{
"one": "1",
},
},
Response: &HeaderOperation{
Add: map[string]string{
"A": "a",
},
},
},
},
{
input: map[string]string{
buildHigressAnnotationKey(requestHeaderAdd): "one 1\n two 2\nthree 3 \n",
buildHigressAnnotationKey(requestHeaderUpdate): "two 2",
buildHigressAnnotationKey(requestHeaderRemove): "one, two,three\n",
buildHigressAnnotationKey(responseHeaderAdd): "A a\nB b\n",
buildHigressAnnotationKey(responseHeaderUpdate): "X x\nY y\n",
buildHigressAnnotationKey(responseHeaderRemove): "x",
},
expect: &HeaderControlConfig{
Request: &HeaderOperation{
Add: map[string]string{
"one": "1",
"two": "2",
"three": "3",
},
Update: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
Response: &HeaderOperation{
Add: map[string]string{
"A": "a",
"B": "b",
},
Update: map[string]string{
"X": "x",
"Y": "y",
},
Remove: []string{"x"},
},
},
},
}
for _, inputCase := range inputCases {
t.Run("", func(t *testing.T) {
config := &Ingress{}
_ = headerControl.Parse(inputCase.input, config, nil)
if !reflect.DeepEqual(inputCase.expect, config.HeaderControl) {
t.Fatal("Should be equal")
}
})
}
}
func TestHeaderControlApplyRoute(t *testing.T) {
headerControl := headerControl{}
inputCases := []struct {
config *Ingress
input *networking.HTTPRoute
expect *networking.HTTPRoute
}{
{
config: &Ingress{},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{},
},
{
config: &Ingress{
HeaderControl: &HeaderControlConfig{},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{},
Response: &networking.Headers_HeaderOperations{},
},
},
},
{
config: &Ingress{
HeaderControl: &HeaderControlConfig{
Request: &HeaderOperation{
Add: map[string]string{
"one": "1",
"two": "2",
"three": "3",
},
Update: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Add: map[string]string{
"one": "1",
"two": "2",
"three": "3",
},
Set: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
Response: &networking.Headers_HeaderOperations{},
},
},
},
{
config: &Ingress{
HeaderControl: &HeaderControlConfig{
Response: &HeaderOperation{
Add: map[string]string{
"A": "a",
"B": "b",
},
Update: map[string]string{
"X": "x",
"Y": "y",
},
Remove: []string{"x"},
},
},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{},
Response: &networking.Headers_HeaderOperations{
Add: map[string]string{
"A": "a",
"B": "b",
},
Set: map[string]string{
"X": "x",
"Y": "y",
},
Remove: []string{"x"},
},
},
},
},
{
config: &Ingress{
HeaderControl: &HeaderControlConfig{
Request: &HeaderOperation{
Update: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
Response: &HeaderOperation{
Add: map[string]string{
"A": "a",
"B": "b",
},
Remove: []string{"x"},
},
},
},
input: &networking.HTTPRoute{},
expect: &networking.HTTPRoute{
Headers: &networking.Headers{
Request: &networking.Headers_HeaderOperations{
Set: map[string]string{
"two": "2",
},
Remove: []string{"one", "two", "three"},
},
Response: &networking.Headers_HeaderOperations{
Add: map[string]string{
"A": "a",
"B": "b",
},
Remove: []string{"x"},
},
},
},
},
}
for _, inputCase := range inputCases {
t.Run("", func(t *testing.T) {
headerControl.ApplyRoute(inputCase.input, inputCase.config)
if !reflect.DeepEqual(inputCase.input, inputCase.expect) {
t.Fatal("Should be equal")
}
})
}
}

View File

@@ -38,5 +38,5 @@ type RouteHandler interface {
type TrafficPolicyHandler interface {
// ApplyTrafficPolicy parsed ingress annotation config reflected on traffic policy
ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress)
ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy, portTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress)
}

View File

@@ -136,14 +136,16 @@ func (l loadBalance) Parse(annotations Annotations, config *Ingress, _ *GlobalCo
return nil
}
func (l loadBalance) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress) {
func (l loadBalance) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy, portTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress) {
loadBalanceConfig := config.LoadBalance
if loadBalanceConfig == nil {
return
}
var loadBalancer *networking.LoadBalancerSettings
if loadBalanceConfig.cookie != nil {
trafficPolicy.LoadBalancer = &networking.LoadBalancerSettings{
loadBalancer = &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_HttpCookie{
@@ -171,18 +173,25 @@ func (l loadBalance) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy_
},
}
}
trafficPolicy.LoadBalancer = &networking.LoadBalancerSettings{
loadBalancer = &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: consistentHash,
},
}
} else {
trafficPolicy.LoadBalancer = &networking.LoadBalancerSettings{
loadBalancer = &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_Simple{
Simple: loadBalanceConfig.simple,
},
}
}
if trafficPolicy != nil {
trafficPolicy.LoadBalancer = loadBalancer
}
if portTrafficPolicy != nil {
portTrafficPolicy.LoadBalancer = loadBalancer
}
}
func isCookieAffinity(annotations Annotations) bool {

View File

@@ -229,7 +229,7 @@ func TestLoadBalanceApplyTrafficPolicy(t *testing.T) {
for _, inputCase := range inputCases {
t.Run("", func(t *testing.T) {
loadBalance.ApplyTrafficPolicy(inputCase.input, inputCase.config)
loadBalance.ApplyTrafficPolicy(nil, inputCase.input, inputCase.config)
if !reflect.DeepEqual(inputCase.input, inputCase.expect) {
t.Fatal("Should be equal")
}

View File

@@ -22,6 +22,7 @@ import (
)
const (
rewritePath = "rewrite-path"
rewriteTarget = "rewrite-target"
useRegex = "use-regex"
upstreamVhost = "upstream-vhost"
@@ -38,6 +39,7 @@ type RewriteConfig struct {
RewriteTarget string
UseRegex bool
RewriteHost string
RewritePath string
}
type rewrite struct{}
@@ -51,8 +53,9 @@ func (r rewrite) Parse(annotations Annotations, config *Ingress, _ *GlobalContex
rewriteConfig.RewriteTarget, _ = annotations.ParseStringASAP(rewriteTarget)
rewriteConfig.UseRegex, _ = annotations.ParseBoolASAP(useRegex)
rewriteConfig.RewriteHost, _ = annotations.ParseStringASAP(upstreamVhost)
rewriteConfig.RewritePath, _ = annotations.ParseStringForHigress(rewritePath)
if rewriteConfig.RewriteTarget != "" {
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
@@ -68,12 +71,22 @@ func (r rewrite) Parse(annotations Annotations, config *Ingress, _ *GlobalContex
func (r rewrite) ApplyRoute(route *networking.HTTPRoute, config *Ingress) {
rewriteConfig := config.Rewrite
if rewriteConfig == nil || (rewriteConfig.RewriteTarget == "" &&
rewriteConfig.RewriteHost == "") {
rewriteConfig.RewriteHost == "" && rewriteConfig.RewritePath == "") {
return
}
route.Rewrite = &networking.HTTPRewrite{}
if rewriteConfig.RewriteTarget != "" {
if rewriteConfig.RewritePath != "" {
route.Rewrite.Uri = rewriteConfig.RewritePath
for _, match := range route.Match {
if strings.HasSuffix(match.Uri.GetPrefix(), "/") {
if !strings.HasSuffix(route.Rewrite.Uri, "/") {
route.Rewrite.Uri += "/"
}
break
}
}
} else if rewriteConfig.RewriteTarget != "" {
route.Rewrite.UriRegex = &networking.RegexMatchAndSubstitute{
Pattern: route.Match[0].Uri.GetRegex(),
Substitution: rewriteConfig.RewriteTarget,
@@ -102,5 +115,5 @@ func NeedRegexMatch(annotations map[string]string) bool {
func needRewriteConfig(annotations Annotations) bool {
return annotations.HasASAP(rewriteTarget) || annotations.HasASAP(useRegex) ||
annotations.HasASAP(upstreamVhost)
annotations.HasASAP(upstreamVhost) || annotations.HasHigress(rewritePath)
}

View File

@@ -124,6 +124,14 @@ func TestRewriteParse(t *testing.T) {
RewriteHost: "test.com",
},
},
{
input: Annotations{
buildHigressAnnotationKey(rewritePath): "/test",
},
expect: &RewriteConfig{
RewritePath: "/test",
},
},
}
for _, testCase := range testCases {
@@ -241,6 +249,87 @@ func TestRewriteApplyRoute(t *testing.T) {
},
},
},
{
config: &Ingress{
Rewrite: &RewriteConfig{
RewriteTarget: "/test",
RewritePath: "/test",
RewriteHost: "test.com",
},
},
input: &networking.HTTPRoute{
Match: []*networking.HTTPMatchRequest{
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{
Regex: "/hello",
},
},
},
},
},
expect: &networking.HTTPRoute{
Match: []*networking.HTTPMatchRequest{
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{
Regex: "/hello",
},
},
},
},
Rewrite: &networking.HTTPRewrite{
Uri: "/test",
Authority: "test.com",
},
},
},
{
config: &Ingress{
Rewrite: &RewriteConfig{
RewritePath: "/test",
},
},
input: &networking.HTTPRoute{
Match: []*networking.HTTPMatchRequest{
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/hello/",
},
},
},
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{
Exact: "/hello",
},
},
},
},
},
expect: &networking.HTTPRoute{
Match: []*networking.HTTPMatchRequest{
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{
Prefix: "/hello/",
},
},
},
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{
Exact: "/hello",
},
},
},
},
Rewrite: &networking.HTTPRewrite{
Uri: "/test/",
},
},
},
}
for _, inputCase := range inputCases {

View File

@@ -75,6 +75,20 @@ func (u upstreamTLS) Parse(annotations Annotations, config *Ingress, _ *GlobalCo
}
}
if sslVerify, err := annotations.ParseStringASAP(proxySSLVerify); err == nil {
if OnOffRegex.MatchString(sslVerify) {
upstreamTLSConfig.SSLVerify = onOffToBool(sslVerify)
}
}
upstreamTLSConfig.SNI, _ = annotations.ParseStringASAP(proxySSLName)
if enableSNI, err := annotations.ParseStringASAP(proxySSLServerName); err == nil {
if OnOffRegex.MatchString(enableSNI) {
upstreamTLSConfig.EnableSNI = onOffToBool(enableSNI)
}
}
secretName, _ := annotations.ParseStringASAP(proxySSLSecret)
namespacedName := util.SplitNamespacedName(secretName)
if namespacedName.Name == "" {
@@ -86,32 +100,19 @@ func (u upstreamTLS) Parse(annotations Annotations, config *Ingress, _ *GlobalCo
}
upstreamTLSConfig.SecretName = namespacedName.String()
if sslVerify, err := annotations.ParseStringASAP(proxySSLVerify); err == nil {
if OnOffRegex.MatchString(sslVerify) {
upstreamTLSConfig.SSLVerify = onOffToBool(sslVerify)
}
}
upstreamTLSConfig.SNI, _ = annotations.ParseStringASAP(proxySSLName)
if enableSNI, err := annotations.ParseStringASAP(proxySSLServerName); err == nil {
if OnOffRegex.MatchString(enableSNI) {
upstreamTLSConfig.SSLVerify = onOffToBool(enableSNI)
}
}
return nil
}
func (u upstreamTLS) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress) {
func (u upstreamTLS) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy, portTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy, config *Ingress) {
if config.UpstreamTLS == nil {
return
}
upstreamTLSConfig := config.UpstreamTLS
var connectionPool *networking.ConnectionPoolSettings
if isH2(upstreamTLSConfig.BackendProtocol) {
trafficPolicy.ConnectionPool = &networking.ConnectionPoolSettings{
connectionPool = &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
H2UpgradePolicy: networking.ConnectionPoolSettings_HTTPSettings_UPGRADE,
},
@@ -125,8 +126,14 @@ func (u upstreamTLS) ApplyTrafficPolicy(trafficPolicy *networking.TrafficPolicy_
} else if isHTTPS(upstreamTLSConfig.BackendProtocol) {
tls = processSimple(config)
}
trafficPolicy.Tls = tls
if trafficPolicy != nil {
trafficPolicy.ConnectionPool = connectionPool
trafficPolicy.Tls = tls
}
if portTrafficPolicy != nil {
portTrafficPolicy.ConnectionPool = connectionPool
portTrafficPolicy.Tls = tls
}
}
func processMTLS(config *Ingress) *networking.ClientTLSSettings {

View File

@@ -47,6 +47,7 @@ func TestUpstreamTLSParse(t *testing.T) {
SSLVerify: true,
SNI: "SSLName",
SecretName: "namespace/SSLSecret",
EnableSNI: true,
},
},
{
@@ -60,9 +61,10 @@ func TestUpstreamTLSParse(t *testing.T) {
},
expect: &UpstreamTLSConfig{
BackendProtocol: "HTTP2",
SSLVerify: false,
SNI: "",
SSLVerify: true,
SNI: "SSLName",
SecretName: "",
EnableSNI: true,
},
},
}
@@ -143,7 +145,7 @@ func TestApplyTrafficPolicy(t *testing.T) {
for _, testCase := range testCases {
t.Run("", func(t *testing.T) {
parser.ApplyTrafficPolicy(testCase.input, testCase.config)
parser.ApplyTrafficPolicy(nil, testCase.input, testCase.config)
if diff := cmp.Diff(testCase.expect, testCase.input); diff != "" {
t.Fatalf("TestApplyTrafficPolicy() mismatch (-want +got): \n%s", diff)
}

View File

@@ -28,9 +28,10 @@ import (
)
type ServiceKey struct {
Namespace string
Name string
Port int32
Namespace string
Name string
ServiceFQDN string
Port int32
}
type WrapperConfig struct {
@@ -98,8 +99,9 @@ type WrapperVirtualService struct {
}
type WrapperTrafficPolicy struct {
TrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy
WrapperConfig *WrapperConfig
TrafficPolicy *networking.TrafficPolicy
PortTrafficPolicy *networking.TrafficPolicy_PortTrafficPolicy
WrapperConfig *WrapperConfig
}
type WrapperDestinationRule struct {

View File

@@ -19,7 +19,6 @@ import (
"fmt"
"path"
"reflect"
"regexp"
"sort"
"strings"
"sync"
@@ -532,40 +531,25 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
Host: rule.Host,
ClusterId: c.options.ClusterId,
}
httpMatch := &networking.HTTPMatchRequest{}
path := httpPath.Path
var pathType common.PathType
originPath := httpPath.Path
if wrapper.AnnotationsConfig.NeedRegexMatch() {
wrapperHttpRoute.OriginPathType = common.Regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: httpPath.Path + ".*"},
}
pathType = common.Regex
} else {
switch *httpPath.PathType {
case ingress.PathTypeExact:
wrapperHttpRoute.OriginPathType = common.Exact
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: httpPath.Path},
}
pathType = common.Exact
case ingress.PathTypePrefix:
wrapperHttpRoute.OriginPathType = common.Prefix
// borrow from implement of official istio code.
if path == "/" {
wrapperVS.ConfiguredDefaultBackend = true
// Optimize common case of / to not needed regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: path},
}
} else {
path = strings.TrimSuffix(path, "/")
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: regexp.QuoteMeta(path) + common.PrefixMatchRegex},
}
pathType = common.Prefix
if httpPath.Path != "/" {
originPath = strings.TrimSuffix(httpPath.Path, "/")
}
}
}
wrapperHttpRoute.OriginPath = path
wrapperHttpRoute.HTTPRoute.Match = []*networking.HTTPMatchRequest{httpMatch}
wrapperHttpRoute.OriginPath = originPath
wrapperHttpRoute.OriginPathType = pathType
wrapperHttpRoute.HTTPRoute.Match = c.generateHttpMatches(pathType, httpPath.Path, wrapperVS)
wrapperHttpRoute.HTTPRoute.Name = common.GenerateUniqueRouteName(c.options.SystemNamespace, wrapperHttpRoute)
ingressRouteBuilder := convertOptions.IngressRouteCache.New(wrapperHttpRoute)
@@ -748,46 +732,31 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
}
for _, httpPath := range rule.HTTP.Paths {
path := httpPath.Path
canary := &common.WrapperHTTPRoute{
HTTPRoute: &networking.HTTPRoute{},
WrapperConfig: wrapper,
Host: rule.Host,
ClusterId: c.options.ClusterId,
}
httpMatch := &networking.HTTPMatchRequest{}
var pathType common.PathType
originPath := httpPath.Path
if wrapper.AnnotationsConfig.NeedRegexMatch() {
canary.OriginPathType = common.Regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: httpPath.Path + ".*"},
}
pathType = common.Regex
} else {
switch *httpPath.PathType {
case ingress.PathTypeExact:
canary.OriginPathType = common.Exact
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: httpPath.Path},
}
pathType = common.Exact
case ingress.PathTypePrefix:
canary.OriginPathType = common.Prefix
// borrow from implement of official istio code.
if path == "/" {
// Optimize common case of / to not needed regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: path},
}
} else {
path = strings.TrimSuffix(path, "/")
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: regexp.QuoteMeta(path) + common.PrefixMatchRegex},
}
pathType = common.Prefix
if httpPath.Path != "/" {
originPath = strings.TrimSuffix(httpPath.Path, "/")
}
}
}
canary.OriginPath = path
canary.HTTPRoute.Match = []*networking.HTTPMatchRequest{httpMatch}
canary.OriginPath = originPath
canary.OriginPathType = pathType
canary.HTTPRoute.Match = c.generateHttpMatches(pathType, httpPath.Path, nil)
canary.HTTPRoute.Name = common.GenerateUniqueRouteName(c.options.SystemNamespace, canary)
ingressRouteBuilder := convertOptions.IngressRouteCache.New(canary)
@@ -879,20 +848,9 @@ func (c *controller) ConvertTrafficPolicy(convertOptions *common.ConvertOptions,
}
if ingressV1Beta.Backend != nil {
serviceKey, err := c.createServiceKey(ingressV1Beta.Backend, cfg.Namespace)
err := c.storeBackendTrafficPolicy(wrapper, ingressV1Beta.Backend, convertOptions.Service2TrafficPolicy)
if err != nil {
IngressLog.Errorf("ignore default service %s within ingress %s/%s", serviceKey.Name, cfg.Namespace, cfg.Name)
} else {
if _, exist := convertOptions.Service2TrafficPolicy[serviceKey]; !exist {
convertOptions.Service2TrafficPolicy[serviceKey] = &common.WrapperTrafficPolicy{
TrafficPolicy: &networking.TrafficPolicy_PortTrafficPolicy{
Port: &networking.PortSelector{
Number: uint32(serviceKey.Port),
},
},
WrapperConfig: wrapper,
}
}
IngressLog.Errorf("ignore default service within ingress %s/%s, since error:%v", cfg.Namespace, cfg.Name, err)
}
}
@@ -902,22 +860,46 @@ func (c *controller) ConvertTrafficPolicy(convertOptions *common.ConvertOptions,
}
for _, httpPath := range rule.HTTP.Paths {
if httpPath.Backend.ServiceName == "" {
continue
}
serviceKey, err := c.createServiceKey(&httpPath.Backend, cfg.Namespace)
err := c.storeBackendTrafficPolicy(wrapper, &httpPath.Backend, convertOptions.Service2TrafficPolicy)
if err != nil {
IngressLog.Errorf("ignore service %s within ingress %s/%s", serviceKey.Name, cfg.Namespace, cfg.Name)
continue
IngressLog.Errorf("ignore service within ingress %s/%s, since error:%v", cfg.Namespace, cfg.Name, err)
}
}
}
if _, exist := convertOptions.Service2TrafficPolicy[serviceKey]; exist {
continue
return nil
}
func (c *controller) storeBackendTrafficPolicy(wrapper *common.WrapperConfig, backend *ingress.IngressBackend, store map[common.ServiceKey]*common.WrapperTrafficPolicy) error {
if backend == nil {
return errors.New("invalid empty backend")
}
if common.ValidateBackendResource(backend.Resource) && wrapper.AnnotationsConfig.Destination != nil {
for _, dest := range wrapper.AnnotationsConfig.Destination.McpDestination {
serviceKey := common.ServiceKey{
Namespace: "mcp",
Name: dest.Destination.Host,
ServiceFQDN: dest.Destination.Host,
}
if _, exist := store[serviceKey]; !exist {
store[serviceKey] = &common.WrapperTrafficPolicy{
TrafficPolicy: &networking.TrafficPolicy{},
WrapperConfig: wrapper,
}
}
}
} else {
if backend.ServiceName == "" {
return nil
}
serviceKey, err := c.createServiceKey(backend, wrapper.Config.Namespace)
if err != nil {
return fmt.Errorf("ignore service %s within ingress %s/%s", serviceKey.Name, wrapper.Config.Namespace, wrapper.Config.Name)
}
convertOptions.Service2TrafficPolicy[serviceKey] = &common.WrapperTrafficPolicy{
TrafficPolicy: &networking.TrafficPolicy_PortTrafficPolicy{
if _, exist := store[serviceKey]; !exist {
store[serviceKey] = &common.WrapperTrafficPolicy{
PortTrafficPolicy: &networking.TrafficPolicy_PortTrafficPolicy{
Port: &networking.PortSelector{
Number: uint32(serviceKey.Port),
},
@@ -926,7 +908,6 @@ func (c *controller) ConvertTrafficPolicy(convertOptions *common.ConvertOptions,
}
}
}
return nil
}
@@ -1180,6 +1161,42 @@ func (c *controller) shouldProcessIngressUpdate(ing *ingress.Ingress) (bool, err
return preProcessed, nil
}
func (c *controller) generateHttpMatches(pathType common.PathType, path string, wrapperVS *common.WrapperVirtualService) []*networking.HTTPMatchRequest {
var httpMatches []*networking.HTTPMatchRequest
httpMatch := &networking.HTTPMatchRequest{}
switch pathType {
case common.Regex:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: path + ".*"},
}
case common.Exact:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: path},
}
case common.Prefix:
if path == "/" {
if wrapperVS != nil {
wrapperVS.ConfiguredDefaultBackend = true
}
// Optimize common case of / to not needed regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: path},
}
} else {
newPath := strings.TrimSuffix(path, "/")
httpMatches = append(httpMatches, c.generateHttpMatches(common.Exact, newPath, wrapperVS)...)
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: newPath + "/"},
}
}
}
httpMatches = append(httpMatches, httpMatch)
return httpMatches
}
// setDefaultMSEIngressOptionalField sets a default value for optional fields when is not defined.
func setDefaultMSEIngressOptionalField(ing *ingress.Ingress) {
if ing == nil {

View File

@@ -19,7 +19,6 @@ import (
"fmt"
"path"
"reflect"
"regexp"
"sort"
"strings"
"sync"
@@ -507,6 +506,7 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
}
wrapperHttpRoutes := make([]*common.WrapperHTTPRoute, 0, len(rule.HTTP.Paths))
for _, httpPath := range rule.HTTP.Paths {
wrapperHttpRoute := &common.WrapperHTTPRoute{
HTTPRoute: &networking.HTTPRoute{},
@@ -514,40 +514,25 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
Host: rule.Host,
ClusterId: c.options.ClusterId,
}
httpMatch := &networking.HTTPMatchRequest{}
path := httpPath.Path
var pathType common.PathType
originPath := httpPath.Path
if wrapper.AnnotationsConfig.NeedRegexMatch() {
wrapperHttpRoute.OriginPathType = common.Regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: httpPath.Path + ".*"},
}
pathType = common.Regex
} else {
switch *httpPath.PathType {
case ingress.PathTypeExact:
wrapperHttpRoute.OriginPathType = common.Exact
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: httpPath.Path},
}
pathType = common.Exact
case ingress.PathTypePrefix:
wrapperHttpRoute.OriginPathType = common.Prefix
// borrow from implement of official istio code.
if path == "/" {
wrapperVS.ConfiguredDefaultBackend = true
// Optimize common case of / to not needed regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: path},
}
} else {
path = strings.TrimSuffix(path, "/")
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: regexp.QuoteMeta(path) + common.PrefixMatchRegex},
}
pathType = common.Prefix
if httpPath.Path != "/" {
originPath = strings.TrimSuffix(httpPath.Path, "/")
}
}
}
wrapperHttpRoute.OriginPath = path
wrapperHttpRoute.HTTPRoute.Match = []*networking.HTTPMatchRequest{httpMatch}
wrapperHttpRoute.OriginPath = originPath
wrapperHttpRoute.OriginPathType = pathType
wrapperHttpRoute.HTTPRoute.Match = c.generateHttpMatches(pathType, httpPath.Path, wrapperVS)
wrapperHttpRoute.HTTPRoute.Name = common.GenerateUniqueRouteName(c.options.SystemNamespace, wrapperHttpRoute)
ingressRouteBuilder := convertOptions.IngressRouteCache.New(wrapperHttpRoute)
@@ -620,6 +605,42 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
return nil
}
func (c *controller) generateHttpMatches(pathType common.PathType, path string, wrapperVS *common.WrapperVirtualService) []*networking.HTTPMatchRequest {
var httpMatches []*networking.HTTPMatchRequest
httpMatch := &networking.HTTPMatchRequest{}
switch pathType {
case common.Regex:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: path + ".*"},
}
case common.Exact:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: path},
}
case common.Prefix:
if path == "/" {
if wrapperVS != nil {
wrapperVS.ConfiguredDefaultBackend = true
}
// Optimize common case of / to not needed regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: path},
}
} else {
newPath := strings.TrimSuffix(path, "/")
httpMatches = append(httpMatches, c.generateHttpMatches(common.Exact, newPath, wrapperVS)...)
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: newPath + "/"},
}
}
}
httpMatches = append(httpMatches, httpMatch)
return httpMatches
}
func (c *controller) ApplyDefaultBackend(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
if wrapper.AnnotationsConfig.IsCanary() {
return nil
@@ -717,46 +738,31 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
}
for _, httpPath := range rule.HTTP.Paths {
path := httpPath.Path
canary := &common.WrapperHTTPRoute{
HTTPRoute: &networking.HTTPRoute{},
WrapperConfig: wrapper,
Host: rule.Host,
ClusterId: c.options.ClusterId,
}
httpMatch := &networking.HTTPMatchRequest{}
var pathType common.PathType
originPath := httpPath.Path
if wrapper.AnnotationsConfig.NeedRegexMatch() {
canary.OriginPathType = common.Regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: httpPath.Path + ".*"},
}
pathType = common.Regex
} else {
switch *httpPath.PathType {
case ingress.PathTypeExact:
canary.OriginPathType = common.Exact
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: httpPath.Path},
}
pathType = common.Exact
case ingress.PathTypePrefix:
canary.OriginPathType = common.Prefix
// borrow from implement of official istio code.
if path == "/" {
// Optimize common case of / to not needed regex
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: path},
}
} else {
path = strings.TrimSuffix(path, "/")
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: regexp.QuoteMeta(path) + common.PrefixMatchRegex},
}
pathType = common.Prefix
if httpPath.Path != "/" {
originPath = strings.TrimSuffix(httpPath.Path, "/")
}
}
}
canary.OriginPath = path
canary.HTTPRoute.Match = []*networking.HTTPMatchRequest{httpMatch}
canary.OriginPath = originPath
canary.OriginPathType = pathType
canary.HTTPRoute.Match = c.generateHttpMatches(pathType, httpPath.Path, nil)
canary.HTTPRoute.Name = common.GenerateUniqueRouteName(c.options.SystemNamespace, canary)
ingressRouteBuilder := convertOptions.IngressRouteCache.New(canary)
@@ -819,6 +825,7 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
} else {
convertOptions.IngressRouteCache.Update(targetRoute)
}
}
}
return nil
@@ -841,20 +848,9 @@ func (c *controller) ConvertTrafficPolicy(convertOptions *common.ConvertOptions,
}
if ingressV1.DefaultBackend != nil {
serviceKey, err := c.createServiceKey(ingressV1.DefaultBackend.Service, cfg.Namespace)
err := c.storeBackendTrafficPolicy(wrapper, ingressV1.DefaultBackend, convertOptions.Service2TrafficPolicy)
if err != nil {
IngressLog.Errorf("ignore default service %s within ingress %s/%s", serviceKey.Name, cfg.Namespace, cfg.Name)
} else {
if _, exist := convertOptions.Service2TrafficPolicy[serviceKey]; !exist {
convertOptions.Service2TrafficPolicy[serviceKey] = &common.WrapperTrafficPolicy{
TrafficPolicy: &networking.TrafficPolicy_PortTrafficPolicy{
Port: &networking.PortSelector{
Number: uint32(serviceKey.Port),
},
},
WrapperConfig: wrapper,
}
}
IngressLog.Errorf("ignore default service within ingress %s/%s, since error:%v", cfg.Namespace, cfg.Name, err)
}
}
@@ -864,22 +860,46 @@ func (c *controller) ConvertTrafficPolicy(convertOptions *common.ConvertOptions,
}
for _, httpPath := range rule.HTTP.Paths {
if httpPath.Backend.Service == nil {
continue
}
serviceKey, err := c.createServiceKey(httpPath.Backend.Service, cfg.Namespace)
err := c.storeBackendTrafficPolicy(wrapper, &httpPath.Backend, convertOptions.Service2TrafficPolicy)
if err != nil {
IngressLog.Errorf("ignore service %s within ingress %s/%s", serviceKey.Name, cfg.Namespace, cfg.Name)
continue
IngressLog.Errorf("ignore service within ingress %s/%s, since error:%v", cfg.Namespace, cfg.Name, err)
}
}
}
if _, exist := convertOptions.Service2TrafficPolicy[serviceKey]; exist {
continue
return nil
}
func (c *controller) storeBackendTrafficPolicy(wrapper *common.WrapperConfig, backend *ingress.IngressBackend, store map[common.ServiceKey]*common.WrapperTrafficPolicy) error {
if backend == nil {
return errors.New("invalid empty backend")
}
if common.ValidateBackendResource(backend.Resource) && wrapper.AnnotationsConfig.Destination != nil {
for _, dest := range wrapper.AnnotationsConfig.Destination.McpDestination {
serviceKey := common.ServiceKey{
Namespace: "mcp",
Name: dest.Destination.Host,
ServiceFQDN: dest.Destination.Host,
}
if _, exist := store[serviceKey]; !exist {
store[serviceKey] = &common.WrapperTrafficPolicy{
TrafficPolicy: &networking.TrafficPolicy{},
WrapperConfig: wrapper,
}
}
}
} else {
if backend.Service == nil {
return nil
}
serviceKey, err := c.createServiceKey(backend.Service, wrapper.Config.Namespace)
if err != nil {
return fmt.Errorf("ignore service %s within ingress %s/%s", serviceKey.Name, wrapper.Config.Namespace, wrapper.Config.Name)
}
convertOptions.Service2TrafficPolicy[serviceKey] = &common.WrapperTrafficPolicy{
TrafficPolicy: &networking.TrafficPolicy_PortTrafficPolicy{
if _, exist := store[serviceKey]; !exist {
store[serviceKey] = &common.WrapperTrafficPolicy{
PortTrafficPolicy: &networking.TrafficPolicy_PortTrafficPolicy{
Port: &networking.PortSelector{
Number: uint32(serviceKey.Port),
},
@@ -888,7 +908,6 @@ func (c *controller) ConvertTrafficPolicy(convertOptions *common.ConvertOptions,
}
}
}
return nil
}

View File

@@ -17,6 +17,8 @@ package ingressv1
import (
"testing"
"github.com/google/go-cmp/cmp"
networking "istio.io/api/networking/v1alpha3"
v1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -76,3 +78,38 @@ func TestShouldProcessIngressUpdate(t *testing.T) {
t.Fatal("should be true")
}
}
func TestGenerateHttpMatches(t *testing.T) {
c := controller{}
tt := []struct {
pathType common.PathType
path string
expect []*networking.HTTPMatchRequest
}{
{
pathType: common.Prefix,
path: "/foo",
expect: []*networking.HTTPMatchRequest{
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: "/foo"},
},
}, {
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: "/foo/"},
},
},
},
},
}
for _, testcase := range tt {
httpMatches := c.generateHttpMatches(testcase.pathType, testcase.path, nil)
for idx, httpMatch := range httpMatches {
if diff := cmp.Diff(httpMatch, testcase.expect[idx]); diff != "" {
t.Errorf("generateHttpMatches() mismatch (-want +got):\n%s", diff)
}
}
}
}

View File

@@ -0,0 +1,7 @@
build --config=clang
build:gcc --cxxopt=-std=c++17
build:clang --action_env=CC=clang --action_env=CXX=clang++
build:clang --action_env=BAZEL_COMPILER=clang
build:clang --linkopt=-fuse-ld=lld
build:clang --cxxopt=-std=c++17

View File

@@ -1,3 +1,17 @@
# 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.
cc_library(
name = "common_util",
hdrs = [
@@ -38,7 +52,7 @@ cc_library(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@com_google_absl//absl/strings:str_format",
"@proxy_wasm_cpp_host//:lib",
"@proxy_wasm_cpp_host//:null_lib",
],
)

View File

@@ -20,6 +20,8 @@
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "common/common_util.h"
namespace Wasm::Common::Http {
std::string_view stripPortFromHost(std::string_view request_host) {
@@ -188,7 +190,7 @@ std::vector<std::string> getAllOfHeader(std::string_view key) {
std::vector<std::string> result;
auto headers = getRequestHeaderPairs()->pairs();
for (auto& header : headers) {
if (absl::EqualsIgnoreCase(header.first, key)) {
if (absl::EqualsIgnoreCase(Wasm::Common::stdToAbsl(header.first), Wasm::Common::stdToAbsl(key))) {
result.push_back(std::string(header.second));
}
}
@@ -197,7 +199,7 @@ std::vector<std::string> getAllOfHeader(std::string_view key) {
void forEachCookie(
std::string_view cookie_header,
const std::function<bool(absl::string_view, absl::string_view)>&
const std::function<bool(std::string_view, std::string_view)>&
cookie_consumer) {
auto cookie_headers = getAllOfHeader(cookie_header);
@@ -223,7 +225,7 @@ void forEachCookie(
v = v.substr(1, v.size() - 2);
}
if (!cookie_consumer(k, v)) {
if (!cookie_consumer(Wasm::Common::abslToStd(k), Wasm::Common::abslToStd(v))) {
return;
}
}
@@ -263,7 +265,7 @@ std::string buildOriginalUri(std::optional<uint32_t> max_path_length) {
auto scheme = scheme_ptr->view();
auto host_ptr = getRequestHeader(Header::Host);
auto host = host_ptr->view();
return absl::StrCat(scheme, "://", host, final_path);
return absl::StrCat(Wasm::Common::stdToAbsl(scheme), "://", Wasm::Common::stdToAbsl(host), Wasm::Common::stdToAbsl(final_path));
}
} // namespace Wasm::Common::Http

View File

@@ -29,17 +29,20 @@ class CompiledGoogleReMatcher {
bool do_program_size_check = true)
: regex_(regex, re2::RE2::Quiet) {
if (!regex_.ok()) {
throw std::runtime_error(regex_.error());
error_ = regex_.error();
return;
}
if (do_program_size_check) {
const auto regex_program_size =
static_cast<uint32_t>(regex_.ProgramSize());
if (regex_program_size > 100) {
throw std::runtime_error("too complex regex: " + regex);
error_ = "too complex regex: " + regex;
}
}
}
const std::string& error() const { return error_; }
bool match(std::string_view value) const {
return re2::RE2::FullMatch(re2::StringPiece(value.data(), value.size()),
regex_);
@@ -56,6 +59,7 @@ class CompiledGoogleReMatcher {
private:
const re2::RE2 regex_;
std::string error_;
};
} // namespace Wasm::Common::Regex

View File

@@ -126,6 +126,12 @@ class RouteRuleMatcher {
LOG_DEBUG("no match config");
return true;
}
if (!config.second && global_auth_ && !global_auth_.value()) {
// No allow set, means no need to check auth if global_auth is false
LOG_DEBUG(
"no allow set found, and global auth is false, no need to auth");
return true;
}
return checkPlugin(config.first.value(), config.second);
}
@@ -220,8 +226,9 @@ class RouteRuleMatcher {
break;
}
}
return is_matched ? std::make_pair(match_config, allow_set)
: std::make_pair(std::nullopt, std::nullopt);
return is_matched || (global_auth_ && global_auth_.value())
? std::make_pair(match_config, allow_set)
: std::make_pair(std::nullopt, std::nullopt);
}
void setEmptyGlobalConfig() { global_config_ = PluginConfig{}; }
@@ -288,6 +295,18 @@ class RouteRuleMatcher {
has_rules = true;
key_count--;
}
auto auth_it = config.find("global_auth");
if (auth_it != config.end()) {
auto global_auth_value = JsonValueAs<bool>(auth_it.value());
if (global_auth_value.second !=
Wasm::Common::JsonParserResultDetail::OK ||
!global_auth_value.first) {
LOG_WARN(
"failed to parse 'global_auth' field in filter configuration.");
return false;
}
global_auth_ = global_auth_value.first.value();
}
PluginConfig plugin_config;
// has other config fields
if (key_count > 0 && parsePluginConfig(config, plugin_config)) {
@@ -455,6 +474,7 @@ class RouteRuleMatcher {
}
bool invalid_config_ = false;
std::optional<bool> global_auth_ = std::nullopt;
std::vector<RuleConfig> rule_config_;
std::vector<AuthRuleConfig> auth_rule_config_;
std::optional<PluginConfig> global_config_ = std::nullopt;

View File

@@ -1,3 +1,17 @@
# 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.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
@@ -31,7 +45,7 @@ cc_library(
visibility = ["//visibility:public"],
alwayslink = 1,
deps = [
"//common:rule_util",
"//common:rule_util_nullvm",
"//common:json_util",
"//common:crypto_util",
"@com_google_absl//absl/strings",

View File

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

View File

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

View File

@@ -308,8 +308,7 @@ bool PluginRootContext::onConfigure(size_t size) {
// Parse configuration JSON string.
if (size > 0 && !configure(size)) {
LOG_WARN("configuration has errors initialization will not continue.");
setInvalidConfig();
return true;
return false;
}
return true;
}

View File

@@ -909,7 +909,8 @@ TEST_F(BasicAuthTest, GlobalDeny) {
}
TEST_F(BasicAuthTest, GlobalWithConsumerDeny) {
std::string configuration = R"(
{
std::string configuration = R"(
{
"consumers" : [
{"credential" : "ok:test", "name" : "consumer_ok"},
@@ -932,36 +933,171 @@ TEST_F(BasicAuthTest, GlobalWithConsumerDeny) {
}
]
})";
BufferBase buffer;
buffer.set({configuration.data(), configuration.size()});
BufferBase buffer;
buffer.set({configuration.data(), configuration.size()});
EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration))
.WillOnce([&buffer](WasmBufferType) { return &buffer; });
EXPECT_TRUE(root_context_->configure(configuration.size()));
EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration))
.WillOnce([&buffer](WasmBufferType) { return &buffer; });
EXPECT_TRUE(root_context_->configure(configuration.size()));
cred_ = "wrong-cred";
route_name_ = "config";
authorization_header_ = "Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
cred_ = "wrong-cred";
route_name_ = "not match";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
authority_ = "www.example.com";
cred_ = "admin2:admin2";
authorization_header_ = "Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
cred_ = "wrong-cred";
route_name_ = "config";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
route_name_ = "config";
cred_ = "admin4:admin4";
authorization_header_ = "Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
authority_ = "www.example.com";
cred_ = "admin2:admin2";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
route_name_ = "config";
cred_ = "admin4:admin4";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
}
{
std::string configuration = R"(
{
"global_auth": true,
"consumers" : [
{"credential" : "ok:test", "name" : "consumer_ok"},
{"credential" : "admin2:admin2", "name" : "consumer2"},
{"credential" : "admin:admin", "name" : "consumer"}
],
"_rules_" : [
{
"_match_route_" : ["test", "config"],
"consumers" : [
{"credential" : "admin3:admin3", "name" : "consumer3"},
{"credential" : "YWRtaW41OmFkbWluNQ==", "name" : "consumer5"}
]
},
{
"_match_domain_" : ["test.com", "*.example.com"],
"consumers" : [
{"credential" : "admin4:admin4", "name" : "consumer4"}
]
}
]
})";
BufferBase buffer;
buffer.set({configuration.data(), configuration.size()});
EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration))
.WillOnce([&buffer](WasmBufferType) { return &buffer; });
EXPECT_TRUE(root_context_->configure(configuration.size()));
cred_ = "wrong-cred";
route_name_ = "not match";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
cred_ = "wrong-cred";
route_name_ = "config";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
authority_ = "www.example.com";
cred_ = "admin2:admin2";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
route_name_ = "config";
cred_ = "admin4:admin4";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
}
}
TEST_F(BasicAuthTest, OnConfigureNoRulesAuth) {
// enable global auth
{
std::string configuration = R"(
{
"consumers" : [
{"credential" : "getuser1:123456", "name" : "consumer1"}
]
})";
BufferBase buffer;
buffer.set({configuration.data(), configuration.size()});
EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration))
.WillOnce([&buffer](WasmBufferType) { return &buffer; });
EXPECT_TRUE(root_context_->configure(configuration.size()));
cred_ = "admin:admin";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_CALL(*mock_context_, sendLocalResponse(401, testing::_, testing::_,
testing::_, testing::_));
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::StopIteration);
cred_ = "getuser1:123456";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
}
// disable global auth
{
std::string configuration = R"(
{
"consumers" : [
{"credential" : "getuser1:123456", "name" : "consumer1"}
],
"global_auth": false
})";
BufferBase buffer;
buffer.set({configuration.data(), configuration.size()});
EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration))
.WillOnce([&buffer](WasmBufferType) { return &buffer; });
EXPECT_TRUE(root_context_->configure(configuration.size()));
cred_ = "admin:admin";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
cred_ = "getuser1:123456";
authorization_header_ =
"Basic " + Base64::encode(cred_.data(), cred_.size());
EXPECT_EQ(context_->onRequestHeaders(0, false),
FilterHeadersStatus::Continue);
}
}
} // namespace basic_auth

View File

@@ -1,3 +1,17 @@
# 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.
load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary")
load("//bazel:wasm.bzl", "declare_wasm_image_targets")
@@ -11,10 +25,10 @@ wasm_cc_binary(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"//common:json_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
"//common:http_util",
"//common:regex_util",
"//common:rule_util",
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
],
)
@@ -31,9 +45,9 @@ cc_library(
"@com_google_absl//absl/strings",
"//common:json_util",
"@proxy_wasm_cpp_host//:lib",
"//common:http_util",
"//common:http_util_nullvm",
"//common:regex_util",
"//common:rule_util",
"//common:rule_util_nullvm",
],
)

View File

@@ -78,13 +78,13 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
LOG_WARN("cannot parse allow");
return false;
}
try {
rule.allow.push_back(
std::make_unique<ReMatcher>(regex.first.value()));
} catch (const std::runtime_error& e) {
LOG_WARN(e.what());
auto re = std::make_unique<ReMatcher>(regex.first.value());
if (!re->error().empty()) {
LOG_WARN(re->error());
return false;
}
rule.allow.push_back(std::move(re));
return true;
})) {
LOG_WARN("failed to parse configuration for allow.");
@@ -96,12 +96,13 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
LOG_WARN("cannot parse deny");
return false;
}
try {
rule.deny.push_back(std::make_unique<ReMatcher>(regex.first.value()));
} catch (const std::runtime_error& e) {
LOG_WARN(e.what());
auto re = std::make_unique<ReMatcher>(regex.first.value());
if (!re->error().empty()) {
LOG_WARN(re->error());
return false;
}
rule.deny.push_back(std::move(re));
return true;
})) {
LOG_WARN("failed to parse configuration for deny.");
@@ -114,8 +115,7 @@ bool PluginRootContext::onConfigure(size_t size) {
// Parse configuration JSON string.
if (size > 0 && !configure(size)) {
LOG_WARN("configuration has errors initialization will not continue.");
setInvalidConfig();
return true;
return false;
}
if (size == 0) {
// support empty config

Some files were not shown because too many files have changed in this diff Show More