mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 21:21:01 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
069b636c10 | ||
|
|
f5edac0c58 | ||
|
|
06b09066a3 | ||
|
|
7ff1d2c414 | ||
|
|
acaf3d899a | ||
|
|
96e7153c8c | ||
|
|
0acb04fffb | ||
|
|
affa1207d2 | ||
|
|
e18557d2ea | ||
|
|
0668eaea1e | ||
|
|
41f892b26d | ||
|
|
a5edad1a84 | ||
|
|
0d4b8ee313 |
68
.github/workflows/latest-release.yaml
vendored
Normal file
68
.github/workflows/latest-release.yaml
vendored
Normal 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
|
||||
```
|
||||
@@ -26,6 +26,7 @@ header:
|
||||
- 'VERSION'
|
||||
- 'tools/'
|
||||
- 'test/README.md'
|
||||
- 'pkg/cmd/hgctl/testdata/config'
|
||||
|
||||
comment: on-failure
|
||||
dependency:
|
||||
|
||||
@@ -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.
|
||||
|
||||
29
cmd/hgctl/main.go
Normal file
29
cmd/hgctl/main.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
55
envoy/1.20/patches/envoy/20230408-basic-auth.patch
Normal file
55
envoy/1.20/patches/envoy/20230408-basic-auth.patch
Normal 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
12
go.mod
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.7.1
|
||||
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.1
|
||||
version: 1.0.0-rc
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
{{- end }}
|
||||
|
||||
defaultConfig:
|
||||
{{- if .Values.global.disableAlpnH2 }}
|
||||
disableAlpnH2: true
|
||||
{{- end }}
|
||||
{{- if .Values.global.meshID }}
|
||||
meshId: {{ .Values.global.meshID }}
|
||||
{{- end }}
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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 }}
|
||||
|
||||
@@ -9,7 +9,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 +21,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 }}
|
||||
@@ -152,7 +152,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,7 +172,7 @@ 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 }}
|
||||
|
||||
@@ -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.1"
|
||||
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
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
dependencies:
|
||||
- name: higress-core
|
||||
repository: file://../core
|
||||
version: 0.7.1
|
||||
version: 1.0.0-rc
|
||||
- name: higress-console
|
||||
repository: https://higress.io/helm-charts/
|
||||
version: 0.1.1
|
||||
digest: sha256:051fbd7b2916d1d0c26839d0e27653f6e42d20e9294bd9eed9628f24c5a7b228
|
||||
generated: "2023-04-03T13:42:23.705379+08:00"
|
||||
version: 0.2.0
|
||||
digest: sha256:0a34765ab2125ccf397e81566b4d81a8dc0742a2477d225aad77d9450e4add94
|
||||
generated: "2023-04-08T23:17:37.193119+08:00"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.7.1
|
||||
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.1
|
||||
version: 1.0.0-rc
|
||||
- name: higress-console
|
||||
repository: "https://higress.io/helm-charts/"
|
||||
version: 0.1.1
|
||||
version: 0.2.0
|
||||
type: application
|
||||
version: 0.7.1
|
||||
version: 1.0.0-rc
|
||||
|
||||
54
istio/1.12/patches/istio/20230408-ignore-ns.patch
Normal file
54
istio/1.12/patches/istio/20230408-ignore-ns.patch
Normal 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
|
||||
19
istio/1.12/patches/proxy/20230408-build-extensions.patch
Normal file
19
istio/1.12/patches/proxy/20230408-build-extensions.patch
Normal 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" \
|
||||
65
pkg/cmd/hgctl/configBootstrap.go
Normal file
65
pkg/cmd/hgctl/configBootstrap.go
Normal 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
|
||||
}
|
||||
64
pkg/cmd/hgctl/configCluster.go
Normal file
64
pkg/cmd/hgctl/configCluster.go
Normal 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
|
||||
}
|
||||
79
pkg/cmd/hgctl/configCmd.go
Normal file
79
pkg/cmd/hgctl/configCmd.go
Normal 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
|
||||
}
|
||||
65
pkg/cmd/hgctl/configEndpoint.go
Normal file
65
pkg/cmd/hgctl/configEndpoint.go
Normal 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
|
||||
}
|
||||
65
pkg/cmd/hgctl/configListener.go
Normal file
65
pkg/cmd/hgctl/configListener.go
Normal 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
|
||||
}
|
||||
151
pkg/cmd/hgctl/configRetriever.go
Normal file
151
pkg/cmd/hgctl/configRetriever.go
Normal 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)
|
||||
}
|
||||
65
pkg/cmd/hgctl/configRoute.go
Normal file
65
pkg/cmd/hgctl/configRoute.go
Normal 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
|
||||
}
|
||||
220
pkg/cmd/hgctl/config_test.go
Normal file
220
pkg/cmd/hgctl/config_test.go
Normal 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
|
||||
}
|
||||
172
pkg/cmd/hgctl/kubernetes/client.go
Normal file
172
pkg/cmd/hgctl/kubernetes/client.go
Normal 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
|
||||
}
|
||||
155
pkg/cmd/hgctl/kubernetes/port-forwarder.go
Normal file
155
pkg/cmd/hgctl/kubernetes/port-forwarder.go
Normal 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
33
pkg/cmd/hgctl/root.go
Normal 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
|
||||
}
|
||||
2247
pkg/cmd/hgctl/testdata/config/input/in.all.json
vendored
Normal file
2247
pkg/cmd/hgctl/testdata/config/input/in.all.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2247
pkg/cmd/hgctl/testdata/config/output/out.all.json
vendored
Normal file
2247
pkg/cmd/hgctl/testdata/config/output/out.all.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1305
pkg/cmd/hgctl/testdata/config/output/out.all.yaml
vendored
Normal file
1305
pkg/cmd/hgctl/testdata/config/output/out.all.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1966
pkg/cmd/hgctl/testdata/config/output/out.bootstrap.json
vendored
Normal file
1966
pkg/cmd/hgctl/testdata/config/output/out.bootstrap.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1121
pkg/cmd/hgctl/testdata/config/output/out.bootstrap.yaml
vendored
Normal file
1121
pkg/cmd/hgctl/testdata/config/output/out.bootstrap.yaml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
98
pkg/cmd/hgctl/testdata/config/output/out.cluster.json
vendored
Normal file
98
pkg/cmd/hgctl/testdata/config/output/out.cluster.json
vendored
Normal 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"
|
||||
}]
|
||||
}
|
||||
67
pkg/cmd/hgctl/testdata/config/output/out.cluster.yaml
vendored
Normal file
67
pkg/cmd/hgctl/testdata/config/output/out.cluster.yaml
vendored
Normal 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'
|
||||
30
pkg/cmd/hgctl/testdata/config/output/out.endpoints.json
vendored
Normal file
30
pkg/cmd/hgctl/testdata/config/output/out.endpoints.json
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
21
pkg/cmd/hgctl/testdata/config/output/out.endpoints.yaml
vendored
Normal file
21
pkg/cmd/hgctl/testdata/config/output/out.endpoints.yaml
vendored
Normal 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
|
||||
77
pkg/cmd/hgctl/testdata/config/output/out.listener.json
vendored
Normal file
77
pkg/cmd/hgctl/testdata/config/output/out.listener.json
vendored
Normal 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"
|
||||
}
|
||||
}]
|
||||
}
|
||||
53
pkg/cmd/hgctl/testdata/config/output/out.listener.yaml
vendored
Normal file
53
pkg/cmd/hgctl/testdata/config/output/out.listener.yaml
vendored
Normal 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'
|
||||
31
pkg/cmd/hgctl/testdata/config/output/out.route.json
vendored
Normal file
31
pkg/cmd/hgctl/testdata/config/output/out.route.json
vendored
Normal 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"
|
||||
}]
|
||||
}
|
||||
21
pkg/cmd/hgctl/testdata/config/output/out.route.yaml
vendored
Normal file
21
pkg/cmd/hgctl/testdata/config/output/out.route.yaml
vendored
Normal 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
81
pkg/cmd/hgctl/utils.go
Normal 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
193
pkg/cmd/hgctl/version.go
Normal 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
29
pkg/cmd/options/global.go
Normal 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
32
pkg/cmd/root.go
Normal 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
120
pkg/cmd/server.go
Normal 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
|
||||
}
|
||||
@@ -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
38
pkg/cmd/version.go
Normal 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
|
||||
}
|
||||
62
pkg/cmd/version/version.go
Normal file
62
pkg/cmd/version/version.go
Normal 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
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -134,37 +134,41 @@ 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)(;.\\*)?$",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
@@ -1180,6 +1149,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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
plugins/wasm-cpp/.bazelrc
Normal file
7
plugins/wasm-cpp/.bazelrc
Normal 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
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,9 +25,9 @@ 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:rule_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -149,8 +149,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;
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -13,10 +27,10 @@ wasm_cc_binary(
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/time",
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
"//common:crypto_util",
|
||||
"//common:http_util",
|
||||
"//common:rule_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -37,8 +51,8 @@ cc_library(
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_host//:lib",
|
||||
"//common:crypto_util",
|
||||
"//common:http_util",
|
||||
"//common:rule_util",
|
||||
"//common:http_util_nullvm",
|
||||
"//common:rule_util_nullvm",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -389,8 +389,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;
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -17,9 +31,9 @@ wasm_cc_binary(
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/time",
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
"//common:http_util",
|
||||
"//common:rule_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -135,11 +135,16 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
|
||||
[&](const json& from_header) -> bool {
|
||||
JSON_FIND_FIELD(from_header, name);
|
||||
JSON_FIELD_VALUE_AS(std::string, from_header, name);
|
||||
JSON_FIND_FIELD(from_header, value_prefix);
|
||||
JSON_FIELD_VALUE_AS(std::string, from_header,
|
||||
value_prefix);
|
||||
from_headers.push_back(FromHeader{
|
||||
from_header_name, from_header_value_prefix});
|
||||
std::string header_value_prefix;
|
||||
auto from_header_value_prefix_json =
|
||||
from_header.find("value_prefix");
|
||||
if (from_header_value_prefix_json != from_header.end()) {
|
||||
JSON_FIELD_VALUE_AS(std::string, from_header,
|
||||
value_prefix);
|
||||
header_value_prefix = from_header_value_prefix;
|
||||
}
|
||||
from_headers.push_back(
|
||||
FromHeader{from_header_name, header_value_prefix});
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse 'from_headers' in consumer: " +
|
||||
@@ -229,6 +234,18 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
|
||||
LOG_INFO("at least one consumer has to be configured for a rule.");
|
||||
return false;
|
||||
}
|
||||
std::vector<std::string> enable_headers;
|
||||
if (!JsonArrayIterate(configuration, "enable_headers",
|
||||
[&](const json& enable_header_json) -> bool {
|
||||
JSON_VALUE_AS(std::string, enable_header_json,
|
||||
enable_header, "invalid item");
|
||||
enable_headers.push_back(enable_header);
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse 'enable_headers'");
|
||||
return false;
|
||||
}
|
||||
rule.enable_headers = std::move(enable_headers);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -307,6 +324,20 @@ Status PluginRootContext::consumerVerify(
|
||||
bool PluginRootContext::checkPlugin(
|
||||
const JwtAuthConfigRule& rule,
|
||||
const std::optional<std::unordered_set<std::string>>& allow_set) {
|
||||
if (!rule.enable_headers.empty()) {
|
||||
bool skip_auth = true;
|
||||
for (const auto& enable_header : rule.enable_headers) {
|
||||
auto header_ptr = getRequestHeader(enable_header);
|
||||
if (header_ptr->size() > 0) {
|
||||
LOG_DEBUG("enable by header: " + header_ptr->toString());
|
||||
skip_auth = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skip_auth) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::optional<Status> err_status;
|
||||
bool verified = false;
|
||||
uint64_t now = getCurrentTimeNanoseconds() / 1e9;
|
||||
@@ -354,8 +385,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;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ struct Consumer {
|
||||
|
||||
struct JwtAuthConfigRule {
|
||||
std::vector<Consumer> consumers;
|
||||
std::vector<std::string> enable_headers;
|
||||
};
|
||||
|
||||
// PluginRootContext is the root context for all streams processed by the
|
||||
|
||||
@@ -84,6 +84,9 @@ class JwtAuthTest : public ::testing::Test {
|
||||
if (header == "Authorization") {
|
||||
*result = jwt_header_;
|
||||
}
|
||||
if (header == "x-custom-header") {
|
||||
*result = custom_header_;
|
||||
}
|
||||
return WasmResult::Ok;
|
||||
});
|
||||
ON_CALL(*mock_context_, addHeaderMapValue(WasmHeaderMapType::RequestHeaders,
|
||||
@@ -118,6 +121,7 @@ class JwtAuthTest : public ::testing::Test {
|
||||
std::string authority_;
|
||||
std::string route_name_;
|
||||
std::string jwt_header_;
|
||||
std::string custom_header_;
|
||||
uint64_t current_time_;
|
||||
};
|
||||
|
||||
@@ -146,7 +150,8 @@ TEST_F(JwtAuthTest, RSA) {
|
||||
}
|
||||
|
||||
TEST_F(JwtAuthTest, OCT) {
|
||||
std::string configuration = R"(
|
||||
{
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers": [
|
||||
{
|
||||
@@ -156,17 +161,65 @@ TEST_F(JwtAuthTest, OCT) {
|
||||
}
|
||||
]
|
||||
})";
|
||||
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()));
|
||||
current_time_ = 1665673819 * 1e9;
|
||||
jwt_header_ =
|
||||
R"(Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxNjY1NjczODE5fQ.7BVJOAobz_xYjsenu_CsYhYbgF1gMcqZSpaeQ8HwKmc)";
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration))
|
||||
.WillOnce([&buffer](WasmBufferType) { return &buffer; });
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
current_time_ = 1665673819 * 1e9;
|
||||
jwt_header_ =
|
||||
R"(Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxNjY1NjczODE5fQ.7BVJOAobz_xYjsenu_CsYhYbgF1gMcqZSpaeQ8HwKmc)";
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
}
|
||||
{
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers": [
|
||||
{
|
||||
"name": "consumer-2",
|
||||
"issuer": "abcd",
|
||||
"jwks": "{\"keys\":[{\"kty\":\"oct\",\"kid\":\"123\",\"k\":\"hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew\",\"alg\":\"HS256\"}]}"
|
||||
}
|
||||
]
|
||||
})";
|
||||
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()));
|
||||
current_time_ = 1665673819 * 1e9;
|
||||
jwt_header_ =
|
||||
R"(Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxNjY1NjczODE5fQ.7BVJOAobz_xYjsenu_CsYhYbgF1gMcqZSpaeQ8HwKm1)";
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
}
|
||||
{
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers": [
|
||||
{
|
||||
"name": "consumer-2",
|
||||
"issuer": "abcd",
|
||||
"jwks": "{\"keys\":[{\"kty\":\"oct\",\"kid\":\"123\",\"k\":\"hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew\",\"alg\":\"HS256\"}]}"
|
||||
}
|
||||
],
|
||||
"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()));
|
||||
current_time_ = 1665673819 * 1e9;
|
||||
jwt_header_ =
|
||||
R"(Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxNjY1NjczODE5fQ.7BVJOAobz_xYjsenu_CsYhYbgF1gMcqZSpaeQ8HwKm1)";
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(JwtAuthTest, AuthZ) {
|
||||
@@ -264,6 +317,97 @@ TEST_F(JwtAuthTest, ClaimToHeader) {
|
||||
FilterHeadersStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(JwtAuthTest, CustomHeader) {
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers": [
|
||||
{
|
||||
"name": "consumer-2",
|
||||
"issuer": "abcd",
|
||||
"from_headers": [
|
||||
{
|
||||
"name": "x-custom-header",
|
||||
"value_prefix": "token "
|
||||
},
|
||||
{
|
||||
"name": "Authorization"
|
||||
}
|
||||
],
|
||||
"jwks": "{\"keys\":[{\"kty\":\"oct\",\"kid\":\"123\",\"k\":\"hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew\",\"alg\":\"HS256\"}]}"
|
||||
}
|
||||
]
|
||||
})";
|
||||
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()));
|
||||
custom_header_ =
|
||||
R"(token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxNjY1NjczODE5fQ.7BVJOAobz_xYjsenu_CsYhYbgF1gMcqZSpaeQ8HwKmc)";
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
custom_header_.clear();
|
||||
jwt_header_ =
|
||||
R"(eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxNjY1NjczODE5fQ.7BVJOAobz_xYjsenu_CsYhYbgF1gMcqZSpaeQ8HwKmc)";
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
}
|
||||
|
||||
TEST_F(JwtAuthTest, SkipAuthHeader) {
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"consumers": [
|
||||
{
|
||||
"name": "consumer-1",
|
||||
"issuer": "abc",
|
||||
"jwks": "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"123\",\"alg\":\"RS256\",\"n\":\"i0B67f1jggT9QJlZ_8QL9QQ56LfurrqDhpuu8BxtVcfxrYmaXaCtqTn7OfCuca7cGHdrJIjq99rz890NmYFZuvhaZ-LMt2iyiSb9LZJAeJmHf7ecguXS_-4x3hvbsrgUDi9tlg7xxbqGYcrco3anmalAFxsbswtu2PAXLtTnUo6aYwZsWA6ksq4FL3-anPNL5oZUgIp3HGyhhLTLdlQcC83jzxbguOim-0OEz-N4fniTYRivK7MlibHKrJfO3xa_6whBS07HW4Ydc37ZN3Rx9Ov3ZyV0idFblU519nUdqp_inXj1eEpynlxH60Ys_aTU2POGZh_25KXGdF_ZC_MSRw\"}]}"
|
||||
},
|
||||
{
|
||||
"name": "consumer-2",
|
||||
"issuer": "abcd",
|
||||
"jwks": "{\"keys\":[{\"kty\":\"oct\",\"kid\":\"123\",\"k\":\"hM0k3AbXBPpKOGg__Ql2Obcq7s60myWDpbHXzgKUQdYo7YCRp0gUqkCnbGSvZ2rGEl4YFkKqIqW7mTHdj-bcqXpNr-NOznEyMpVPOIlqG_NWVC3dydBgcsIZIdD-MR2AQceEaxriPA_VmiUCwfwL2Bhs6_i7eolXoY11EapLQtutz0BV6ZxQQ4dYUmct--7PLNb4BWJyQeWu0QfbIthnvhYllyl2dgeLTEJT58wzFz5HeNMNz8ohY5K0XaKAe5cepryqoXLhA-V-O1OjSG8lCNdKS09OY6O0fkyweKEtuDfien5tHHSsHXoAxYEHPFcSRL4bFPLZ0orTt1_4zpyfew\",\"alg\":\"HS256\"}]}"
|
||||
}
|
||||
],
|
||||
"enable_headers": ["x-custom-header"],
|
||||
"_rules_": [{
|
||||
"_match_route_": [
|
||||
"test1"
|
||||
],
|
||||
"allow": [
|
||||
"consumer-1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"_match_route_": [
|
||||
"test2"
|
||||
],
|
||||
"allow": [
|
||||
"consumer-2"
|
||||
]
|
||||
}
|
||||
]
|
||||
})";
|
||||
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()));
|
||||
current_time_ = 1665673819 * 1e9;
|
||||
jwt_header_ =
|
||||
R"(Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9.eyJpc3MiOiJhYmNkIiwic3ViIjoidGVzdCIsImlhdCI6MTY2NTY2MDUyNywiZXhwIjoxNjY1NjczODE5fQ.7BVJOAobz_xYjsenu_CsYhYbgF1gMcqZSpaeQ8HwKmc)";
|
||||
route_name_ = "test1";
|
||||
custom_header_ = "123";
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_,
|
||||
testing::_, testing::_));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
custom_header_ = "";
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
}
|
||||
|
||||
} // namespace jwt_auth
|
||||
} // namespace null_plugin
|
||||
} // namespace proxy_wasm
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -12,9 +26,9 @@ 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:rule_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -33,8 +47,8 @@ cc_library(
|
||||
"@com_google_absl//absl/time",
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_host//:lib",
|
||||
"//common:http_util",
|
||||
"//common:rule_util",
|
||||
"//common:http_util_nullvm",
|
||||
"//common:rule_util_nullvm",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
123
plugins/wasm-cpp/extensions/key_auth/README_EN.md
Normal file
123
plugins/wasm-cpp/extensions/key_auth/README_EN.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Features
|
||||
The `key-auth` plug-in implements the authentication function based on the API Key, supports parsing the API Key from the URL parameter or request header of the HTTP request, and verifies whether the API Key has permission to access.
|
||||
|
||||
# Configuration field
|
||||
|
||||
| Name | Data Type | Parameter requirements | Default| Description |
|
||||
| ----------- | --------------- | -------------------------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------- |
|
||||
| `consumers` | array of object | Required | - | Configure the caller of the service to authenticate the request. |
|
||||
| `keys` | array of string | Required | - | The name of the source field of the API Key, which can be a URL parameter or an HTTP request header name. |
|
||||
| `in_query` | bool | At least one of `in_query` and `in_header` must be true. | true | When configured true, the gateway will try to parse the API Key from the URL parameters. |
|
||||
| `in_header` | bool | The same as above. | true | The same as above. |
|
||||
| `_rules_` | array of object | Optional | - | Configure the access list of a specific route or domain name for authenticating requests. |
|
||||
|
||||
|
||||
The configuration fields of each item in `consumers` are described as follows:
|
||||
|
||||
| Name | Data Type | Parameter requirements | Default | Description |
|
||||
| ------------ | --------- | -----------------------| ------ | ------------------------------------------- |
|
||||
| `credential` | string | Required | - | Configure the consumer's access credentials. |
|
||||
| `name` | string | Required | - | Configure the name of the consumer. |
|
||||
|
||||
The configuration fields of each item in `_rules_` are described as follows:
|
||||
|
||||
| Name | Data Type | Parameter requirements | Default| Description |
|
||||
| ---------------- | --------------- | --------------------------------------------------------------------- | ------ | -------------------------------------------------- |
|
||||
| `_match_route_` | array of string | Optional,Optionally fill in one of `_match_route_`, `_match_domain_`. | - | Configure the route name to match. |
|
||||
| `_match_domain_` | array of string | Optional,Optionally fill in one of `_match_route_`, `_match_domain_`. | - | Configure the domain name to match. |
|
||||
| `allow` | array of string | Required | - | For requests that meet the matching conditions, configure the name of the consumer that is allowed to access. |
|
||||
|
||||
**Warning:**
|
||||
- If the `_rules_` field is not configured, authentication will be enabled for all routes of the current gateway instance by default;
|
||||
- For a request that passes authentication, an `X-Mse-Consumer` field will be added to the request header to identify the name of the caller.
|
||||
|
||||
# Example configuration
|
||||
|
||||
## Enabled for specific routes or domains
|
||||
|
||||
The following configuration will enable Key Auth authentication and authentication for gateway-specific routes or domain names. Note that the `credential` field can not be repeated.
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- credential: 2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
name: consumer1
|
||||
- credential: c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
|
||||
name: consumer2
|
||||
keys:
|
||||
- apikey
|
||||
in_query: true
|
||||
# Use the _rules_ field for fine-grained rule configuration
|
||||
_rules_:
|
||||
# Rule 1: Match by route name to take effect
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
allow:
|
||||
- consumer1
|
||||
# Rule 2: Take effect by domain name matching
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- consumer2
|
||||
```
|
||||
|
||||
The `route-a` and `route-b` specified in `_match_route_` in this example are the route names filled in when creating the gateway route. When these two routes are matched, calls whose `name` is `consumer1` will be allowed Access by callers, other callers are not allowed to access;
|
||||
|
||||
`*.example.com` and `test.com` specified in `_match_domain_` in this example are used to match the domain name of the request. When the domain name matches, the caller whose `name` is `consumer2` will be allowed to access, and other calls access is not allowed.
|
||||
|
||||
### Depending on this configuration, the following requests would allow access:
|
||||
|
||||
Assume that the following request will match the route-a route:
|
||||
|
||||
**Set the API Key in the url parameter**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test?apikey=2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
```
|
||||
**Set the API Key in the http request header**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test -H 'x-api-key: 2bda943c-ba2b-11ec-ba07-00163e1250b5'
|
||||
```
|
||||
|
||||
After the authentication is passed, an `X-Mse-Consumer` field will be added to the header of the request. In this example, its value is `consumer1`, which is used to identify the name of the caller.
|
||||
|
||||
### The following requests will deny access:
|
||||
|
||||
**The request does not provide an API Key, return 401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test
|
||||
```
|
||||
**The API Key provided by the request is not authorized to access, return 401**
|
||||
```bash
|
||||
curl http://xxx.hello.com/test?apikey=926d90ac-ba2e-11ec-ab68-00163e1250b5
|
||||
```
|
||||
|
||||
**The caller matched according to the API Key provided in the request has no access rights, return 403**
|
||||
```bash
|
||||
# consumer2 is not in the allow list of route-a
|
||||
curl http://xxx.hello.com/test?apikey=c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
|
||||
```
|
||||
|
||||
## Gateway instance level enabled
|
||||
|
||||
The following configuration does not specify the `_rules_` field, so Key Auth authentication will be enabled at the gateway instance level.
|
||||
|
||||
```yaml
|
||||
consumers:
|
||||
- credential: 2bda943c-ba2b-11ec-ba07-00163e1250b5
|
||||
name: consumer1
|
||||
- credential: c8c8e9ca-558e-4a2d-bb62-e700dcc40e35
|
||||
name: consumer2
|
||||
keys:
|
||||
- apikey
|
||||
in_query: true
|
||||
```
|
||||
|
||||
# Error code
|
||||
|
||||
| HTTP status code | Error information | Reason |
|
||||
| ---------------- | --------------------------------------------------------- | -------------------------------------------- |
|
||||
| 401 | No API key found in request. | API not provided by request Key. |
|
||||
| 401 | Request denied by Key Auth check. Invalid API key. | Current API Key access is not allowed. |
|
||||
| 403 | Request denied by Basic Auth check. Unauthorized consumer. | The requested caller does not have access. |
|
||||
|
||||
@@ -211,8 +211,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;
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -13,9 +27,9 @@ 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:rule_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -34,8 +48,8 @@ cc_library(
|
||||
"@com_google_absl//absl/strings",
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_host//:lib",
|
||||
"//common:http_util",
|
||||
"//common:rule_util",
|
||||
"//common:http_util_nullvm",
|
||||
"//common:rule_util_nullvm",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
|
||||
namespace {
|
||||
@@ -40,11 +41,16 @@ bool getToken(int rule_id, const std::string &key) {
|
||||
for (int i = 0; i < maxGetTokenRetry; i++) {
|
||||
if (WasmResult::Ok !=
|
||||
getSharedData(tokenBucketKey, &token_bucket_data, &cas)) {
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
uint64_t token_left =
|
||||
*reinterpret_cast<const uint64_t *>(token_bucket_data->data());
|
||||
LOG_DEBUG(absl::StrFormat(
|
||||
"ratelimit get token: id:%d, tokenBucketKey:%s, token left:%u", rule_id,
|
||||
tokenBucketKey, token_left));
|
||||
if (token_left == 0) {
|
||||
LOG_DEBUG(absl::StrFormat("get token failed, id:%d, tokenBucketKey:%s",
|
||||
rule_id, tokenBucketKey));
|
||||
return false;
|
||||
}
|
||||
token_left -= 1;
|
||||
@@ -52,12 +58,18 @@ bool getToken(int rule_id, const std::string &key) {
|
||||
tokenBucketKey,
|
||||
{reinterpret_cast<const char *>(&token_left), sizeof(token_left)}, cas);
|
||||
if (res == WasmResult::Ok) {
|
||||
LOG_DEBUG(
|
||||
absl::StrFormat("ratelimit token update success: id:%d, "
|
||||
"tokenBucketKey:%s, token left:%u",
|
||||
rule_id, tokenBucketKey, token_left));
|
||||
return true;
|
||||
}
|
||||
if (res == WasmResult::CasMismatch) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
LOG_WARN(absl::StrFormat("got invalid result:%d, id:%d, tokenBucketKey:%s",
|
||||
res, rule_id, tokenBucketKey));
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_WARN("get token failed with cas mismatch");
|
||||
@@ -86,12 +98,21 @@ void refillToken(const std::vector<std::pair<int, LimitItem>> &rules) {
|
||||
if (now - last_update < rule.second.refill_interval_nanosec) {
|
||||
continue;
|
||||
}
|
||||
LOG_DEBUG(
|
||||
absl::StrFormat("ratelimit rule need refilled, id:%s, "
|
||||
"lastRefilledKey:%s, now:%u, last_update:%u",
|
||||
id, lastRefilledKey, now, last_update));
|
||||
// Otherwise, try set last updated time. If updated failed because of cas
|
||||
// mismatch, the bucket is going to be refilled by other VMs.
|
||||
auto res = setSharedData(
|
||||
lastRefilledKey, {reinterpret_cast<const char *>(&now), sizeof(now)},
|
||||
last_update_cas);
|
||||
if (res == WasmResult::CasMismatch) {
|
||||
LOG_DEBUG(
|
||||
absl::StrFormat("ratelimit update lastRefilledKey casmismatch, the "
|
||||
"bucket is going to be refilled by other VMs, id:%s, "
|
||||
"lastRefilledKey:%s",
|
||||
id, lastRefilledKey));
|
||||
continue;
|
||||
}
|
||||
do {
|
||||
@@ -115,6 +136,10 @@ void refillToken(const std::vector<std::pair<int, LimitItem>> &rules) {
|
||||
last_update_cas)) {
|
||||
continue;
|
||||
}
|
||||
LOG_DEBUG(
|
||||
absl::StrFormat("ratelimit token refilled: id:%s, "
|
||||
"tokenBucketKey:%s, token left:%u",
|
||||
id, tokenBucketKey, token_left));
|
||||
break;
|
||||
} while (true);
|
||||
}
|
||||
@@ -138,6 +163,10 @@ bool initializeTokenBucket(
|
||||
setSharedData(tokenBucketKey,
|
||||
{reinterpret_cast<const char *>(&rule.second.max_tokens),
|
||||
sizeof(uint64_t)});
|
||||
LOG_INFO(absl::StrFormat(
|
||||
"ratelimit rule created: id:%s, lastRefilledKey:%s, "
|
||||
"tokenBucketKey:%s, max_tokens:%u",
|
||||
id, lastRefilledKey, tokenBucketKey, rule.second.max_tokens));
|
||||
continue;
|
||||
}
|
||||
// reconfigure
|
||||
@@ -172,6 +201,10 @@ bool initializeTokenBucket(
|
||||
}
|
||||
break;
|
||||
} while (true);
|
||||
LOG_INFO(absl::StrFormat(
|
||||
"ratelimit rule reconfigured: id:%s, lastRefilledKey:%s, "
|
||||
"tokenBucketKey:%s, max_tokens:%u",
|
||||
id, lastRefilledKey, tokenBucketKey, rule.second.max_tokens));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ bool PluginRootContext::checkPlugin(int rule_id,
|
||||
return true;
|
||||
}
|
||||
if (!getToken(rule_id, key)) {
|
||||
LOG_INFO(absl::StrCat("request rate limited by key: ", key));
|
||||
tooManyRequest();
|
||||
return false;
|
||||
}
|
||||
@@ -181,8 +182,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;
|
||||
}
|
||||
const auto& rules = getRules();
|
||||
for (const auto& rule : rules) {
|
||||
@@ -191,7 +191,7 @@ bool PluginRootContext::onConfigure(size_t size) {
|
||||
}
|
||||
}
|
||||
initializeTokenBucket(limits_);
|
||||
proxy_set_tick_period_milliseconds(1000);
|
||||
proxy_set_tick_period_milliseconds(500);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,9 +25,9 @@ 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:rule_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -30,8 +44,8 @@ cc_library(
|
||||
"@com_google_absl//absl/strings",
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_host//:lib",
|
||||
"//common:http_util",
|
||||
"//common:rule_util",
|
||||
"//common:http_util_nullvm",
|
||||
"//common:rule_util_nullvm",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -108,24 +108,24 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
|
||||
return false;
|
||||
}
|
||||
if (!JsonArrayIterate(
|
||||
configuration, "block_bodies", [&](const json& item) -> bool {
|
||||
configuration, "block_bodys", [&](const json& item) -> bool {
|
||||
auto body = JsonValueAs<std::string>(item);
|
||||
if (body.second != Wasm::Common::JsonParserResultDetail::OK) {
|
||||
LOG_WARN("cannot parse block_bodies");
|
||||
LOG_WARN("cannot parse block_bodys");
|
||||
return false;
|
||||
}
|
||||
if (rule.case_sensitive) {
|
||||
rule.block_bodies.push_back(std::move(body.first.value()));
|
||||
rule.block_bodys.push_back(std::move(body.first.value()));
|
||||
} else {
|
||||
rule.block_bodies.push_back(
|
||||
rule.block_bodys.push_back(
|
||||
absl::AsciiStrToLower(body.first.value()));
|
||||
}
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse configuration for block_bodies.");
|
||||
LOG_WARN("failed to parse configuration for block_bodys.");
|
||||
return false;
|
||||
}
|
||||
if (rule.block_bodies.empty() && rule.block_headers.empty() &&
|
||||
if (rule.block_bodys.empty() && rule.block_headers.empty() &&
|
||||
rule.block_urls.empty()) {
|
||||
LOG_WARN("there is no block rules");
|
||||
return false;
|
||||
@@ -137,8 +137,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;
|
||||
}
|
||||
@@ -197,7 +196,7 @@ bool PluginRootContext::checkHeader(const RequestBlockConfigRule& rule,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!rule.block_bodies.empty()) {
|
||||
if (!rule.block_bodys.empty()) {
|
||||
check_body = true;
|
||||
}
|
||||
return true;
|
||||
@@ -212,7 +211,7 @@ bool PluginRootContext::checkBody(const RequestBlockConfigRule& rule,
|
||||
bodystr = absl::AsciiStrToLower(request_body);
|
||||
body = bodystr;
|
||||
}
|
||||
for (const auto& block_body : rule.block_bodies) {
|
||||
for (const auto& block_body : rule.block_bodys) {
|
||||
if (absl::StrContains(body, block_body)) {
|
||||
sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {});
|
||||
return false;
|
||||
|
||||
@@ -45,7 +45,7 @@ struct RequestBlockConfigRule {
|
||||
bool case_sensitive = true;
|
||||
std::vector<std::string> block_urls;
|
||||
std::vector<std::string> block_headers;
|
||||
std::vector<std::string> block_bodies;
|
||||
std::vector<std::string> block_bodys;
|
||||
};
|
||||
|
||||
// PluginRootContext is the root context for all streams processed by the
|
||||
|
||||
@@ -128,7 +128,7 @@ TEST_F(RequestBlockTest, CaseSensitive) {
|
||||
{
|
||||
"block_urls": ["?foo=bar", "swagger.html"],
|
||||
"block_headers": ["headerKey", "headerValue"],
|
||||
"block_bodies": ["Hello World"]
|
||||
"block_bodys": ["Hello World"]
|
||||
})";
|
||||
|
||||
config_.set({configuration.data(), configuration.size()});
|
||||
@@ -188,7 +188,7 @@ TEST_F(RequestBlockTest, CaseInsensitive) {
|
||||
"blocked_code": 404,
|
||||
"block_urls": ["?foo=bar", "swagger.html"],
|
||||
"block_headers": ["headerKey", "headerValue"],
|
||||
"block_bodies": ["Hello World"]
|
||||
"block_bodys": ["Hello World"]
|
||||
})";
|
||||
|
||||
config_.set({configuration.data(), configuration.size()});
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -10,8 +24,8 @@ wasm_cc_binary(
|
||||
deps = [
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
"//common:http_util",
|
||||
"@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -30,7 +44,7 @@ cc_library(
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@proxy_wasm_cpp_host//:lib",
|
||||
"//common:http_util",
|
||||
"//common:http_util_nullvm",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ spec:
|
||||
# 跟上面例子一样,这个配置会全局生效,但如果被下面规则匹配到,则会改为执行命中规则的配置
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
matchRules:
|
||||
matchRules:
|
||||
# 路由级生效配置
|
||||
- ingress:
|
||||
- default/foo
|
||||
|
||||
@@ -102,8 +102,6 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config HttpCallConfig, log wr
|
||||
defer proxywasm.ResumeHttpRequest()
|
||||
if statusCode != http.StatusOK {
|
||||
log.Errorf("http call failed, status: %d", statusCode)
|
||||
proxywasm.SendHttpResponse(http.StatusInternalServerError, nil,
|
||||
[]byte("http call failed"), -1)
|
||||
return
|
||||
}
|
||||
// avoid protocol error
|
||||
|
||||
@@ -64,6 +64,74 @@ var HTTPRouteCanaryHeader = suite.ConformanceTest{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo",
|
||||
Host: "canary.higress.io",
|
||||
Headers: map[string]string{"traffic-split-higress": "true"},
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo/bar",
|
||||
Host: "canary.higress.io",
|
||||
Headers: map[string]string{"traffic-split-higress": "true"},
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v3",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo",
|
||||
Host: "canary.higress.io",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v3",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo/bar",
|
||||
Host: "canary.higress.io",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -53,3 +53,59 @@ spec:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/canary: "true"
|
||||
nginx.ingress.kubernetes.io/canary-by-header: "traffic-split-higress"
|
||||
nginx.ingress.kubernetes.io/canary-by-header-value: "true"
|
||||
name: ingress-echo-canary-value-prefix
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: canary.higress.io
|
||||
http:
|
||||
paths:
|
||||
- path: /foo
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
- path: /foo
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-echo-prefix
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: canary.higress.io
|
||||
http:
|
||||
paths:
|
||||
- path: /foo
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v3
|
||||
port:
|
||||
number: 8080
|
||||
- path: /foo
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v3
|
||||
port:
|
||||
number: 8080
|
||||
|
||||
@@ -22,13 +22,13 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteIgnoreCaseMatch)
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteEnableIgnoreCase)
|
||||
}
|
||||
|
||||
var HTTPRouteIgnoreCaseMatch = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteIgnoreCaseMatch",
|
||||
var HTTPRouteEnableIgnoreCase = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteEnableIgnoreCase",
|
||||
Description: "A Ingress in the higress-conformance-infra namespace that ignores URI case in HTTP match.",
|
||||
Manifests: []string{"tests/httproute-ignore-case-match.yaml"},
|
||||
Manifests: []string{"tests/httproute-enable-ignore-case.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
@@ -65,6 +65,40 @@ var HTTPRouteIgnoreCaseMatch = suite.ConformanceTest{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case3: enable ignoreCase",
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/BAR",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case4: enable ignoreCase",
|
||||
TargetBackend: "infra-backend-v3",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/CAT/ok",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -32,3 +32,17 @@ spec:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
- pathType: Prefix
|
||||
path: "/bar"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
- pathType: Prefix
|
||||
path: "/cat/"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v3
|
||||
port:
|
||||
number: 8080
|
||||
143
test/ingress/conformance/tests/httproute-match-path.go
Normal file
143
test/ingress/conformance/tests/httproute-match-path.go
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteMatchPath)
|
||||
}
|
||||
|
||||
var HTTPRouteMatchPath = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteMatchPath",
|
||||
Description: "A Ingress in the higress-conformance-infra namespace that match different path.",
|
||||
Manifests: []string{"tests/httproute-match-path.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case1: normal request",
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case2: path is '/bar' and match prefix path successfully",
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/bar",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case2: path is '/bar' and match prefix path failed",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/bard",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 404,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case3: path is '/cat/' and match prefix path successfully",
|
||||
TargetBackend: "infra-backend-v3",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/cat",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TestCaseName: "case4: path is '/cat/' and match prefix path successfully",
|
||||
TargetBackend: "infra-backend-v3",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/cat/ok",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo/",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("HTTPRoute Match different path Cases", func(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||
}
|
||||
})
|
||||
|
||||
},
|
||||
}
|
||||
53
test/ingress/conformance/tests/httproute-match-path.yaml
Normal file
53
test/ingress/conformance/tests/httproute-match-path.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: httproute-match-path
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: "foo.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/foo"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
- pathType: Exact
|
||||
path: "/foo"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
- pathType: Prefix
|
||||
path: "/bar"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
- pathType: Prefix
|
||||
path: "/cat/"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v3
|
||||
port:
|
||||
number: 8080
|
||||
@@ -57,7 +57,7 @@ func TestHigressConformanceTests(t *testing.T) {
|
||||
tests.HTTPRouteRewriteHost,
|
||||
tests.HTTPRouteCanaryHeader,
|
||||
tests.HTTPRouteEnableCors,
|
||||
tests.HTTPRouteIgnoreCaseMatch,
|
||||
tests.HTTPRouteEnableIgnoreCase,
|
||||
tests.HTTPRouteMatchMethods,
|
||||
tests.HTTPRouteMatchQueryParams,
|
||||
tests.HTTPRouteMatchHeaders,
|
||||
@@ -69,6 +69,7 @@ func TestHigressConformanceTests(t *testing.T) {
|
||||
tests.HTTPRouteCanaryHeaderWithCustomizedHeader,
|
||||
tests.HTTPRouteWhitelistSourceRange,
|
||||
tests.HTTPRouteCanaryWeight,
|
||||
tests.HTTPRouteMatchPath,
|
||||
}
|
||||
|
||||
cSuite.Run(t, higressTests)
|
||||
|
||||
183
tools/hack/get-hgctl.sh
Normal file
183
tools/hack/get-hgctl.sh
Normal file
@@ -0,0 +1,183 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http:www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
#!/usr/bin/env bash
|
||||
|
||||
: "${BINARY_NAME:="hgctl"}"
|
||||
: "${hgctl_INSTALL_DIR:="/usr/local/bin"}"
|
||||
|
||||
export VERSION
|
||||
|
||||
HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)"
|
||||
HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)"
|
||||
HAS_GIT="$(type "git" &> /dev/null && echo true || echo false)"
|
||||
|
||||
# initArch discovers the architecture for this system.
|
||||
initArch() {
|
||||
ARCH=$(uname -m)
|
||||
case $ARCH in
|
||||
armv5*) ARCH="armv5";;
|
||||
armv6*) ARCH="armv6";;
|
||||
armv7*) ARCH="arm";;
|
||||
aarch64) ARCH="arm64";;
|
||||
x86) ARCH="386";;
|
||||
x86_64) ARCH="amd64";;
|
||||
i686) ARCH="386";;
|
||||
i386) ARCH="386";;
|
||||
esac
|
||||
}
|
||||
|
||||
# initOS discovers the operating system for this system.
|
||||
initOS() {
|
||||
OS="$(uname|tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
case "$OS" in
|
||||
# Minimalist GNU for Windows
|
||||
mingw*|cygwin*) OS='windows';;
|
||||
esac
|
||||
}
|
||||
|
||||
# runs the given command as root (detects if we are root already)
|
||||
runAsRoot() {
|
||||
if [ $EUID -ne 0 ]; then
|
||||
sudo "${@}"
|
||||
else
|
||||
"${@}"
|
||||
fi
|
||||
}
|
||||
|
||||
# verifySupported checks that the os/arch combination is supported for
|
||||
# binary builds, as well whether or not necessary tools are present.
|
||||
verifySupported() {
|
||||
local supported="darwin-amd64\ndarwin-arm64\nlinux-amd64\nlinux-arm64\n"
|
||||
if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
|
||||
echo "No prebuilt binary for ${OS}-${ARCH}."
|
||||
echo "To build from source, go to https://github.com/alibaba/higress"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${HAS_CURL}" != "true" ] && [ "${HAS_WGET}" != "true" ]; then
|
||||
echo "Either curl or wget is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${HAS_GIT}" != "true" ]; then
|
||||
echo "[WARNING] Could not find git. It is required for plugin installation."
|
||||
fi
|
||||
}
|
||||
|
||||
# checkDesiredVersion checks if the desired version is available.
|
||||
checkDesiredVersion() {
|
||||
if [ "$VERSION" == "" ]; then
|
||||
# Get tag from release URL
|
||||
local latest_release_url="https://github.com/alibaba/higress/releases"
|
||||
if [ "${HAS_CURL}" == "true" ]; then
|
||||
VERSION=$(curl -Ls $latest_release_url | grep 'href="/alibaba/higress/releases/tag/v[0-9]*.[0-9]*.[0-9]*\"' | sed -E 's/.*\/alibaba\/higress\/releases\/tag\/(v[0-9\.]+)".*/\1/g' | head -1)
|
||||
elif [ "${HAS_WGET}" == "true" ]; then
|
||||
VERSION=$(wget $latest_release_url -O - 2>&1 | grep 'href="/alibaba/higress/releases/tag/v[0-9]*.[0-9]*.[0-9]*\"' | sed -E 's/.*\/alibaba\/higress\/releases\/tag\/(v[0-9\.]+)".*/\1/g' | head -1)
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# checkhgctlInstalledVersion checks which version of hgctl is installed and
|
||||
# if it needs to be changed.
|
||||
checkhgctlInstalledVersion() {
|
||||
if [[ -f "${hgctl_INSTALL_DIR}/${BINARY_NAME}" ]]; then
|
||||
version=$("${hgctl_INSTALL_DIR}/${BINARY_NAME}" version --client | grep -Eo "v[0-9]+\.[0-9]+.*" )
|
||||
if [[ "$version" == "$VERSION" ]]; then
|
||||
echo "hgctl ${version} is already ${VERSION:-latest}"
|
||||
return 0
|
||||
else
|
||||
echo "hgctl ${VERSION} is available. Changing from version ${version}."
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# downloadFile downloads the latest binary package
|
||||
# for that binary.
|
||||
downloadFile() {
|
||||
hgctl_DIST="hgctl_${VERSION}_${OS}_${ARCH}.tar.gz"
|
||||
DOWNLOAD_URL="https://github.com/alibaba/higress/releases/download/$VERSION/$hgctl_DIST"
|
||||
hgctl_TMP_ROOT="$(mktemp -dt hgctl-installer-XXXXXX)"
|
||||
hgctl_TMP_FILE="$hgctl_TMP_ROOT/$hgctl_DIST"
|
||||
echo "Downloading $DOWNLOAD_URL"
|
||||
if [ "${HAS_CURL}" == "true" ]; then
|
||||
curl -SsL "$DOWNLOAD_URL" -o "$hgctl_TMP_FILE"
|
||||
elif [ "${HAS_WGET}" == "true" ]; then
|
||||
wget -q -O "$hgctl_TMP_FILE" "$DOWNLOAD_URL"
|
||||
fi
|
||||
}
|
||||
|
||||
# installFile installs the hgctl binary.
|
||||
installFile() {
|
||||
hgctl_TMP="$hgctl_TMP_ROOT/$BINARY_NAME"
|
||||
mkdir -p "$hgctl_TMP"
|
||||
tar xf "$hgctl_TMP_FILE" -C "$hgctl_TMP"
|
||||
hgctl_TMP_BIN="$hgctl_TMP/out/${OS}_${ARCH}/hgctl"
|
||||
echo "Preparing to install $BINARY_NAME into ${hgctl_INSTALL_DIR}"
|
||||
runAsRoot cp "$hgctl_TMP_BIN" "$hgctl_INSTALL_DIR/$BINARY_NAME"
|
||||
echo "$BINARY_NAME installed into $hgctl_INSTALL_DIR/$BINARY_NAME"
|
||||
}
|
||||
|
||||
# fail_trap is executed if an error occurs.
|
||||
fail_trap() {
|
||||
result=$?
|
||||
if [ "$result" != "0" ]; then
|
||||
if [[ -n "$INPUT_ARGUMENTS" ]]; then
|
||||
echo "Failed to install $BINARY_NAME with the arguments provided: $INPUT_ARGUMENTS"
|
||||
else
|
||||
echo "Failed to install $BINARY_NAME"
|
||||
fi
|
||||
echo -e "\tFor support, go to https://github.com/alibaba/higress."
|
||||
fi
|
||||
cleanup
|
||||
exit $result
|
||||
}
|
||||
|
||||
# testVersion tests the installed client to make sure it is working.
|
||||
testVersion() {
|
||||
set +e
|
||||
if ! [ "$(command -v $BINARY_NAME)" ]; then
|
||||
echo "$BINARY_NAME not found. Is $hgctl_INSTALL_DIR on your PATH?"
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
}
|
||||
|
||||
# cleanup temporary files.
|
||||
cleanup() {
|
||||
if [[ -d "${hgctl_TMP_ROOT:-}" ]]; then
|
||||
rm -rf "$hgctl_TMP_ROOT"
|
||||
fi
|
||||
}
|
||||
|
||||
# Execution
|
||||
|
||||
#Stop execution on any error
|
||||
trap "fail_trap" EXIT
|
||||
set -e
|
||||
|
||||
initArch
|
||||
initOS
|
||||
verifySupported
|
||||
checkDesiredVersion
|
||||
if ! checkhgctlInstalledVersion; then
|
||||
downloadFile
|
||||
installFile
|
||||
fi
|
||||
testVersion
|
||||
cleanup
|
||||
Reference in New Issue
Block a user