Compare commits

..

37 Commits

Author SHA1 Message Date
澄潭
8c76ae26bb update console version (#427) 2023-07-10 21:01:09 +08:00
Kent Dong
c1250aec2e fix: Fix the incompatibility issue with Windows using pipeline (#426) 2023-07-10 20:53:54 +08:00
Kent Dong
02bc319eef fix: Remove two debug logs (#424) 2023-07-10 20:16:05 +08:00
Kent Dong
28892cf3ae feat: Add a GitHub action to deploy standalone packages to OSS (#421) 2023-07-08 21:51:59 +08:00
澄潭
bee03a37a4 Release 1.1 (#412) 2023-07-07 17:53:44 +08:00
VIKI ZHAO
2a97921d2b AI proxy plugin (#420) 2023-07-07 15:30:04 +08:00
Jun
d4dbaba760 deepcopy tracing configuration and fix code format (#411) 2023-07-06 10:02:45 +08:00
纪卓志
d718870b65 feat: get_match_config returns with rule_id for stateful plugins (#406) 2023-07-04 19:59:43 +08:00
Jun
b65446fa25 add tracing in higress-config configmap (#409) 2023-07-04 19:59:11 +08:00
Xunzhuo
3fd37abab7 refactor: e2e infras (#407)
Signed-off-by: bitliu <bitliu@tencent.com>
2023-06-30 17:28:10 +08:00
澄潭
81e467b624 support gateway in other ns (#405) 2023-06-29 11:00:02 +08:00
Hinsteny Hisoka
736eea6cf9 feat: Support dubbo group for http2rpc (#404) 2023-06-29 10:55:34 +08:00
rinfx
c32e1ab69b Go WAF Plugin (#400) 2023-06-28 19:25:36 +08:00
纪卓志
fc05a3b256 feat: support rewrite config after parse_rule_config invoked (#401) 2023-06-28 14:22:28 +08:00
澄潭
9fc2760b7d support higress-cn helm index (#399) 2023-06-28 09:42:29 +08:00
纪卓志
be88647752 feat: extract boilerplate code of on_configure to function (DRY) (#396) 2023-06-28 09:42:19 +08:00
澄潭
a56172095a fix debug configz (#398) 2023-06-27 16:42:38 +08:00
Woa
f3270123ba fix: split REGISTRY into BUILDER_REGISTRY and REGISTRY (#395) 2023-06-25 19:52:17 +08:00
Jun
5e2d62406b fix cond unlock (#394) 2023-06-25 09:39:42 +08:00
Kent Dong
39cab9d724 fix: Add docker image URLs to output (#391) 2023-06-21 14:02:32 +08:00
Kent Dong
89865733f6 feat: Support building and pushing Higress docker images via GitHub actions (#388) 2023-06-21 10:18:23 +08:00
Hinsteny Hisoka
1ccf9195b2 Add e2e testing description for wasm-plugin-go (#389) 2023-06-21 10:17:03 +08:00
澄潭
7d2a05ef1c Update README.md 2023-06-19 10:48:48 +08:00
纪卓志
32c2acefda feat: add wasm-rust sdk with say-hello simple extension (#350) 2023-06-19 10:40:53 +08:00
Hinsteny Hisoka
ea7b581e26 Support HTTP/JSON to dubbo (#340) 2023-06-19 10:40:28 +08:00
Kent Dong
ac2f0a5545 feat: Pilot debug APIs support remote anonymous requests (#386) 2023-06-19 09:36:52 +08:00
Kent Dong
51d7124454 release: Prepare for releasing v1.0.1 (#367) 2023-06-16 18:23:17 +08:00
Hinsteny Hisoka
ec6a185adc feat: support only build one plugin to testing specified WasmPlugin (#371)
Co-authored-by: 澄潭 <zty98751@alibaba-inc.com>
2023-06-15 15:21:54 +08:00
Hinsteny Hisoka
f9ffda288b feature: e2e framework supports testing wasm plugins (#369) 2023-06-08 11:00:30 +08:00
Kent Dong
2dbe41324a fix: Fix some compatibility issues related to older versions of K8s (#363) 2023-06-06 18:02:47 +08:00
Qianglin Li
80d6ecfddb feat: support full path regex annotation (#286)
Signed-off-by: charlie <qianglin98@qq.com>
2023-06-02 18:56:22 +08:00
Ink33
b23fae7a12 ci: enable building files caching (#323)
Signed-off-by: Ink33 <Ink33@smlk.org>
Co-authored-by: Xunzhuo <bitliu@tencent.com>
2023-06-01 11:35:37 +08:00
WeixinX
2c19d97252 feat: Add e2e testcases for 'auth-tls-secret' and 'ssl-cipher' (#354) 2023-05-30 11:08:31 +08:00
Xunzhuo
efd7ccd5fe fix: e2e refers (#345) 2023-05-26 12:02:39 +08:00
Xunzhuo
81fd0d6386 fix: e2e refers (#345) 2023-05-26 12:01:50 +08:00
Jun
176ddc6963 Plugin cors (#349) 2023-05-25 19:21:21 +08:00
WeixinX
44637c2449 fix: Fix e2e 'request header control' and add some testcases (#352) 2023-05-25 15:51:39 +08:00
239 changed files with 32100 additions and 578 deletions

View File

@@ -23,6 +23,28 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- run: git stash # restore patch
# test
- name: Run Coverage Tests
run: GOPROXY="https://proxy.golang.org,direct" make go.test.coverage
@@ -38,15 +60,37 @@ jobs:
runs-on: ubuntu-latest
needs: [lint,coverage-test]
steps:
- name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: "checkout ${{ github.ref }}"
uses: actions/checkout@v3
- name: Setup Golang Caches
uses: actions/cache@v3
with:
fetch-depth: 2
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- run: git stash # restore patch
- name: "Build Higress Binary"
run: GOPROXY="https://proxy.golang.org,direct" make build
@@ -63,20 +107,82 @@ jobs:
steps:
- uses: actions/checkout@v3
ingress-conformance-test:
higress-conformance-test:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v3
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- run: git stash # restore patch
- name: "Run Higress E2E Conformance Tests"
run: GOPROXY="https://proxy.golang.org,direct" make higress-conformance-test
higress-wasmplugin-test:
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v3
- name: "Run Ingress Conformance Tests"
run: GOPROXY="https://proxy.golang.org,direct" make ingress-conformance-test
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- run: git stash # restore patch
- name: "Run Ingress WasmPlugins Tests"
run: GOPROXY="https://proxy.golang.org,direct" make higress-wasmplugin-test
publish:
runs-on: ubuntu-latest
needs: [ingress-conformance-test,gateway-conformance-test]
needs: [higress-conformance-test,gateway-conformance-test,higress-wasmplugin-test]
steps:
- uses: actions/checkout@v3

View File

@@ -0,0 +1,144 @@
name: Build Docker Images and Push to Image Registry
on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
build-controller-image:
runs-on: ubuntu-latest
environment:
name: image-registry-controller
env:
CONTROLLER_IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
CONTROLLER_IMAGE_NAME: ${{ vars.CONTROLLER_IMAGE_NAME || 'higress/higress' }}
steps:
- name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- name: Calculate Docker metadata
id: docker-meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.CONTROLLER_IMAGE_REGISTRY }}/${{ env.CONTROLLER_IMAGE_NAME }}
tags: |
type=sha
type=ref,event=tag
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: ${{ env.CONTROLLER_IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build Docker Image and Push
run: |
GOPROXY="https://proxy.golang.org,direct" make docker-build
BUILT_IMAGE="higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress"
readarray -t IMAGES <<< "${{ steps.docker-meta.outputs.tags }}"
for image in ${IMAGES[@]}; do
echo "Image: $image"
docker tag $BUILT_IMAGE:$GITHUB_SHA $image
docker push $image
done
build-pilot-image:
runs-on: ubuntu-latest
environment:
name: image-registry-pilot
env:
PILOT_IMAGE_REGISTRY: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }}
PILOT_IMAGE_NAME: ${{ vars.PILOT_IMAGE_NAME || 'higress/pilot' }}
steps:
- name: "Checkout ${{ github.ref }}"
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: "Setup Go"
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Setup Golang Caches
uses: actions/cache@v3
with:
path: |-
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
restore-keys: ${{ runner.os }}-go
- name: Setup Submodule Caches
uses: actions/cache@v3
with:
path: |-
envoy
istio
external
.git/modules
key: ${{ runner.os }}-submodules-${{ github.run_id }}
restore-keys: ${{ runner.os }}-submodules
- name: Calculate Docker metadata
id: docker-meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.PILOT_IMAGE_REGISTRY }}/${{ env.PILOT_IMAGE_NAME }}
tags: |
type=sha
type=ref,event=tag
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: ${{ env.PILOT_IMAGE_REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build Docker Image and Push
run: |
sudo GOPROXY="https://proxy.golang.org,direct" make build-istio
BUILT_IMAGE="higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/pilot"
readarray -t IMAGES <<< "${{ steps.docker-meta.outputs.tags }}"
for image in ${IMAGES[@]}; do
echo "Image: $image"
docker tag $BUILT_IMAGE:$GITHUB_SHA $image
docker push $image
done

View File

@@ -0,0 +1,37 @@
name: Deploy Standalone to OSS
on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
deploy-to-oss:
runs-on: ubuntu-latest
environment:
name: oss
steps:
# Step 1
- name: Checkout
uses: actions/checkout@v3
# Step 2
- id: package
name: Prepare Standalone Package
run: |
mkdir ./artifact
cp ./tools/get-higress.sh ./artifact
LOCAL_RELEASE_URL="https://github.com/higress-group/higress-standalone/releases"
VERSION=$(curl -Ls $LOCAL_RELEASE_URL | grep 'href="/higress-group/higress-standalone/releases/tag/v[0-9]*.[0-9]*.[0-9]*\"' | sed -E 's/.*\/higress-group\/higress-standalone\/releases\/tag\/(v[0-9\.]+)".*/\1/g' | head -1)
DOWNLOAD_URL="https://github.com/higress-group/higress-standalone/archive/refs/tags/${VERSION}.tar.gz"
curl -SsL "$DOWNLOAD_URL" -o "./artifact/higress-${VERSION}.tar.gz"
echo -n "$VERSION" > ./artifact/VERSION
echo "Version=$VERSION"
# Step 3
- name: Upload to OSS
uses: doggycool/ossutil-github-action@master
with:
ossArgs: 'cp -r -u ./artifact/ oss://higress-website-cn-hongkong/standalone/'
accessKey: ${{ secrets.ACCESS_KEYID }}
accessSecret: ${{ secrets.ACCESS_KEYSECRET }}
endpoint: oss-cn-hongkong.aliyuncs.com

View File

@@ -4,10 +4,13 @@ on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
deploy-to-oss:
runs-on: ubuntu-latest
environment:
name: oss
steps:
# Step 1
- name: Checkout
@@ -38,6 +41,8 @@ jobs:
helmv3 dependency build helm/higress
helmv3 package helm/higress --debug --app-version ${{steps.calc-version.outputs.version}} --version ${{steps.calc-version.outputs.version}} -d ./artifact
helmv3 repo index --url https://higress.io/helm-charts/ --merge ./artifact/index.yaml ./artifact
cp ./artifact/index.yaml ./artifact/cn-index.yaml
sed -i 's/higress\.io/higress\.cn/g' ./artifact/cn-index.yaml
# Step 5
- name: Upload to OSS
uses: doggycool/ossutil-github-action@master

4
.gitignore vendored
View File

@@ -12,4 +12,6 @@ bazel-out
bazel-testlogs
bazel-wasm-cpp
tools/bin/
helm/**/charts/**.tgz
helm/**/charts/**.tgz
target/
tools/hack/cluster.conf

View File

@@ -128,6 +128,9 @@ build-gateway: prebuild external/package/envoy.tar.gz
build-istio: prebuild
cd external/istio; rm -rf out; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.pilot" make docker
build-wasmplugins:
./tools/hack/build-wasm-plugins.sh
pre-install:
cp api/kubernetes/customresourcedefinitions.gen.yaml helm/core/crds
@@ -139,12 +142,15 @@ install: pre-install
cd helm/higress; helm dependency build
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
ENVOY_LATEST_IMAGE_TAG ?= 1.0.0
ISTIO_LATEST_IMAGE_TAG ?= 1.0.0
ENVOY_LATEST_IMAGE_TAG ?= 1.1.0
ISTIO_LATEST_IMAGE_TAG ?= 1.1.0
install-dev: pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'
install-dev-wasmplugin: build-wasmplugins pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true' --set 'global.volumeWasmPlugins=true'
uninstall:
helm uninstall higress -n higress-system
@@ -193,9 +199,13 @@ include tools/lint.mk
.PHONY: gateway-conformance-test
gateway-conformance-test:
# ingress-conformance-test runs ingress api conformance tests.
.PHONY: ingress-conformance-test
ingress-conformance-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev run-ingress-e2e-test delete-cluster
# higress-conformance-test runs ingress api conformance tests.
.PHONY: higress-conformance-test
higress-conformance-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev run-higress-e2e-test delete-cluster
# higress-wasmplugin-test runs ingress wasmplugin tests.
.PHONY: higress-wasmplugin-test
higress-wasmplugin-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev-wasmplugin run-higress-e2e-test-wasmplugin delete-cluster
# create-cluster creates a kube cluster with kind.
.PHONY: create-cluster
@@ -208,16 +218,33 @@ delete-cluster: $(tools/kind) ## Delete kind cluster.
$(tools/kind) delete cluster --name higress
# kube-load-image loads a local built docker image into kube cluster.
# dubbo-provider-demo和nacos-standlone-rc3的镜像已经上传到阿里云镜像库第一次需要先拉到本地
# docker pull registry.cn-hangzhou.aliyuncs.com/hinsteny/dubbo-provider-demo:0.0.1
# docker pull registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3:1.0.0-RC3
.PHONY: kube-load-image
kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster using the provided $IMAGE and $TAG.
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress $(TAG)
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/dubbo-provider-demo 0.0.1
tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3
tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/dubbo-provider-demo 0.0.1
tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3
# run-ingress-e2e-test starts to run ingress e2e tests.
.PHONY: run-ingress-e2e-test
run-ingress-e2e-test:
# run-higress-e2e-test starts to run ingress e2e tests.
.PHONY: run-higress-e2e-test
run-higress-e2e-test:
@echo -e "\n\033[36mRunning higress conformance tests...\033[0m"
@echo -e "\n\033[36mWaiting higress-controller to be ready...\033[0m\n"
kubectl wait --timeout=10m -n higress-system deployment/higress-controller --for=condition=Available
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
kubectl wait --timeout=10m -n higress-system deployment/higress-gateway --for=condition=Available
go test -v -tags conformance ./test/ingress/e2e_test.go --ingress-class=higress --debug=true
go test -v -tags conformance ./test/e2e/e2e_test.go --ingress-class=higress --debug=true
# run-higress-e2e-test starts to run ingress e2e tests.
.PHONY: run-higress-e2e-test-wasmplugin
run-higress-e2e-test-wasmplugin:
@echo -e "\n\033[36mRunning higress conformance tests...\033[0m"
@echo -e "\n\033[36mWaiting higress-controller to be ready...\033[0m\n"
kubectl wait --timeout=10m -n higress-system deployment/higress-controller --for=condition=Available
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
kubectl wait --timeout=10m -n higress-system deployment/higress-gateway --for=condition=Available
go test -v -tags conformance ./test/e2e/e2e_test.go -isWasmPluginTest=true --ingress-class=higress --debug=true

View File

@@ -125,7 +125,7 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
社区交流群:
![image](https://img.alicdn.com/imgextra/i1/O1CN01d7LmWu1rMB71rfRhA_!!6000000005616-2-tps-720-405.png)
![image](https://img.alicdn.com/imgextra/i1/O1CN01KWonlE1DkpqaYVTiC_!!6000000000255-0-tps-720-405.jpg)
开发者群:

View File

@@ -1 +1 @@
v1.0.0
v1.1.0

View File

@@ -104,6 +104,88 @@ spec:
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
"helm.sh/resource-policy": keep
name: http2rpcs.networking.higress.io
spec:
group: networking.higress.io
names:
categories:
- higress-io
kind: Http2Rpc
listKind: Http2RpcList
plural: http2rpcs
singular: http2rpc
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
properties:
spec:
oneOf:
- not:
anyOf:
- required:
- dubbo
- required:
- grpc
- required:
- dubbo
- required:
- grpc
properties:
dubbo:
properties:
group:
type: string
methods:
items:
properties:
headersAttach:
type: string
httpMethods:
items:
type: string
type: array
httpPath:
type: string
params:
items:
properties:
paramKey:
type: string
paramSource:
type: string
paramType:
type: string
type: object
type: array
serviceMethod:
type: string
type: object
type: array
service:
type: string
version:
type: string
type: object
grpc:
type: object
type: object
status:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
// 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.
syntax = "proto3";
import "google/api/field_behavior.proto";
// $schema: higress.networking.v1.Http2Rpc
// $title: Http2Rpc
// $description: Configuration affecting service discovery from multi registries
// $mode: none
package higress.networking.v1;
option go_package = "github.com/alibaba/higress/api/networking/v1";
// <!-- crd generation tags
// +cue-gen:Http2Rpc:groupName:networking.higress.io
// +cue-gen:Http2Rpc:version:v1
// +cue-gen:Http2Rpc:storageVersion
// +cue-gen:Http2Rpc:annotations:helm.sh/resource-policy=keep
// +cue-gen:Http2Rpc:subresource:status
// +cue-gen:Http2Rpc:scope:Namespaced
// +cue-gen:Http2Rpc:resource:categories=higress-io,plural=Http2Rpcs
// +cue-gen:Http2Rpc:preserveUnknownFields:false
// -->
//
// <!-- go code generation tags
// +kubetype-gen
// +kubetype-gen:groupVersion=networking.higress.io/v1
// +genclient
// +k8s:deepcopy-gen=true
// -->
message Http2Rpc {
oneof destination {
DubboService dubbo = 1;
GrpcService grpc = 2;
}
}
message DubboService {
string service = 1 [(google.api.field_behavior) = REQUIRED];
string version = 2 [(google.api.field_behavior) = REQUIRED];
string group = 3 [(google.api.field_behavior) = OPTIONAL];
repeated Method methods = 4 [(google.api.field_behavior) = REQUIRED];
}
message Method {
string service_method = 1 [(google.api.field_behavior) = REQUIRED];
string headers_attach = 2 [(google.api.field_behavior) = OPTIONAL];
string http_path = 3 [(google.api.field_behavior) = REQUIRED];
repeated string http_methods = 4 [(google.api.field_behavior) = REQUIRED];
repeated Param params = 5;
}
message Param {
string param_source = 1 [(google.api.field_behavior) = REQUIRED];
string param_key = 2 [(google.api.field_behavior) = REQUIRED];
string param_type = 3 [(google.api.field_behavior) = REQUIRED];
}
message GrpcService {
}

View File

@@ -0,0 +1,121 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: networking/v1/http_2_rpc.proto
package v1
import (
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
_ "istio.io/gogo-genproto/googleapis/google/api"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// DeepCopyInto supports using Http2Rpc within kubernetes types, where deepcopy-gen is used.
func (in *Http2Rpc) DeepCopyInto(out *Http2Rpc) {
p := proto.Clone(in).(*Http2Rpc)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Http2Rpc. Required by controller-gen.
func (in *Http2Rpc) DeepCopy() *Http2Rpc {
if in == nil {
return nil
}
out := new(Http2Rpc)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Http2Rpc. Required by controller-gen.
func (in *Http2Rpc) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using DubboService within kubernetes types, where deepcopy-gen is used.
func (in *DubboService) DeepCopyInto(out *DubboService) {
p := proto.Clone(in).(*DubboService)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DubboService. Required by controller-gen.
func (in *DubboService) DeepCopy() *DubboService {
if in == nil {
return nil
}
out := new(DubboService)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new DubboService. Required by controller-gen.
func (in *DubboService) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using Method within kubernetes types, where deepcopy-gen is used.
func (in *Method) DeepCopyInto(out *Method) {
p := proto.Clone(in).(*Method)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Method. Required by controller-gen.
func (in *Method) DeepCopy() *Method {
if in == nil {
return nil
}
out := new(Method)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Method. Required by controller-gen.
func (in *Method) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using Param within kubernetes types, where deepcopy-gen is used.
func (in *Param) DeepCopyInto(out *Param) {
p := proto.Clone(in).(*Param)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Param. Required by controller-gen.
func (in *Param) DeepCopy() *Param {
if in == nil {
return nil
}
out := new(Param)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new Param. Required by controller-gen.
func (in *Param) DeepCopyInterface() interface{} {
return in.DeepCopy()
}
// DeepCopyInto supports using GrpcService within kubernetes types, where deepcopy-gen is used.
func (in *GrpcService) DeepCopyInto(out *GrpcService) {
p := proto.Clone(in).(*GrpcService)
*out = *p
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GrpcService. Required by controller-gen.
func (in *GrpcService) DeepCopy() *GrpcService {
if in == nil {
return nil
}
out := new(GrpcService)
in.DeepCopyInto(out)
return out
}
// DeepCopyInterface is an autogenerated deepcopy function, copying the receiver, creating a new GrpcService. Required by controller-gen.
func (in *GrpcService) DeepCopyInterface() interface{} {
return in.DeepCopy()
}

View File

@@ -0,0 +1,78 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: networking/v1/http_2_rpc.proto
package v1
import (
bytes "bytes"
fmt "fmt"
github_com_gogo_protobuf_jsonpb "github.com/gogo/protobuf/jsonpb"
proto "github.com/gogo/protobuf/proto"
_ "istio.io/gogo-genproto/googleapis/google/api"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// MarshalJSON is a custom marshaler for Http2Rpc
func (this *Http2Rpc) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for Http2Rpc
func (this *Http2Rpc) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for DubboService
func (this *DubboService) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for DubboService
func (this *DubboService) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for Method
func (this *Method) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for Method
func (this *Method) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for Param
func (this *Param) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for Param
func (this *Param) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
// MarshalJSON is a custom marshaler for GrpcService
func (this *GrpcService) MarshalJSON() ([]byte, error) {
str, err := Http_2RpcMarshaler.MarshalToString(this)
return []byte(str), err
}
// UnmarshalJSON is a custom unmarshaler for GrpcService
func (this *GrpcService) UnmarshalJSON(b []byte) error {
return Http_2RpcUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}
var (
Http_2RpcMarshaler = &github_com_gogo_protobuf_jsonpb.Marshaler{}
Http_2RpcUnmarshaler = &github_com_gogo_protobuf_jsonpb.Unmarshaler{AllowUnknownFields: true}
)

View File

@@ -41,6 +41,8 @@ func Resource(resource string) schema.GroupResource {
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Http2Rpc{},
&Http2RpcList{},
&McpBridge{},
&McpBridgeList{},
)

View File

@@ -25,6 +25,48 @@ import (
// please upgrade the proto package
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// <!-- crd generation tags
// +cue-gen:Http2Rpc:groupName:networking.higress.io
// +cue-gen:Http2Rpc:version:v1
// +cue-gen:Http2Rpc:storageVersion
// +cue-gen:Http2Rpc:annotations:helm.sh/resource-policy=keep
// +cue-gen:Http2Rpc:subresource:status
// +cue-gen:Http2Rpc:scope:Namespaced
// +cue-gen:Http2Rpc:resource:categories=higress-io,plural=Http2Rpcs
// +cue-gen:Http2Rpc:preserveUnknownFields:false
// -->
//
// <!-- go code generation tags
// +kubetype-gen
// +kubetype-gen:groupVersion=networking.higress.io/v1
// +genclient
// +k8s:deepcopy-gen=true
// -->
type Http2Rpc struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec defines the implementation of this definition.
// +optional
Spec networkingv1.Http2Rpc `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status v1alpha1.IstioStatus `json:"status"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Http2RpcList is a collection of Http2Rpcs.
type Http2RpcList struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Items []Http2Rpc `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// please upgrade the proto package
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// <!-- crd generation tags
// +cue-gen:McpBridge:groupName:networking.higress.io
// +cue-gen:McpBridge:version:v1

View File

@@ -23,6 +23,67 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Http2Rpc) DeepCopyInto(out *Http2Rpc) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Http2Rpc.
func (in *Http2Rpc) DeepCopy() *Http2Rpc {
if in == nil {
return nil
}
out := new(Http2Rpc)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Http2Rpc) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Http2RpcList) DeepCopyInto(out *Http2RpcList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Http2Rpc, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Http2RpcList.
func (in *Http2RpcList) DeepCopy() *Http2RpcList {
if in == nil {
return nil
}
out := new(Http2RpcList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Http2RpcList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *McpBridge) DeepCopyInto(out *McpBridge) {
*out = *in

View File

@@ -0,0 +1,140 @@
// 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.
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
"context"
networkingv1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeHttp2Rpcs implements Http2RpcInterface
type FakeHttp2Rpcs struct {
Fake *FakeNetworkingV1
ns string
}
var http2rpcsResource = schema.GroupVersionResource{Group: "networking.higress.io", Version: "v1", Resource: "http2rpcs"}
var http2rpcsKind = schema.GroupVersionKind{Group: "networking.higress.io", Version: "v1", Kind: "Http2Rpc"}
// Get takes name of the http2Rpc, and returns the corresponding http2Rpc object, and an error if there is any.
func (c *FakeHttp2Rpcs) Get(ctx context.Context, name string, options v1.GetOptions) (result *networkingv1.Http2Rpc, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(http2rpcsResource, c.ns, name), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}
// List takes label and field selectors, and returns the list of Http2Rpcs that match those selectors.
func (c *FakeHttp2Rpcs) List(ctx context.Context, opts v1.ListOptions) (result *networkingv1.Http2RpcList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(http2rpcsResource, http2rpcsKind, c.ns, opts), &networkingv1.Http2RpcList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &networkingv1.Http2RpcList{ListMeta: obj.(*networkingv1.Http2RpcList).ListMeta}
for _, item := range obj.(*networkingv1.Http2RpcList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested http2Rpcs.
func (c *FakeHttp2Rpcs) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(http2rpcsResource, c.ns, opts))
}
// Create takes the representation of a http2Rpc and creates it. Returns the server's representation of the http2Rpc, and an error, if there is any.
func (c *FakeHttp2Rpcs) Create(ctx context.Context, http2Rpc *networkingv1.Http2Rpc, opts v1.CreateOptions) (result *networkingv1.Http2Rpc, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(http2rpcsResource, c.ns, http2Rpc), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}
// Update takes the representation of a http2Rpc and updates it. Returns the server's representation of the http2Rpc, and an error, if there is any.
func (c *FakeHttp2Rpcs) Update(ctx context.Context, http2Rpc *networkingv1.Http2Rpc, opts v1.UpdateOptions) (result *networkingv1.Http2Rpc, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(http2rpcsResource, c.ns, http2Rpc), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeHttp2Rpcs) UpdateStatus(ctx context.Context, http2Rpc *networkingv1.Http2Rpc, opts v1.UpdateOptions) (*networkingv1.Http2Rpc, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(http2rpcsResource, "status", c.ns, http2Rpc), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}
// Delete takes name of the http2Rpc and deletes it. Returns an error if one occurs.
func (c *FakeHttp2Rpcs) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(http2rpcsResource, c.ns, name), &networkingv1.Http2Rpc{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeHttp2Rpcs) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(http2rpcsResource, c.ns, listOpts)
_, err := c.Fake.Invokes(action, &networkingv1.Http2RpcList{})
return err
}
// Patch applies the patch and returns the patched http2Rpc.
func (c *FakeHttp2Rpcs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *networkingv1.Http2Rpc, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(http2rpcsResource, c.ns, name, pt, data, subresources...), &networkingv1.Http2Rpc{})
if obj == nil {
return nil, err
}
return obj.(*networkingv1.Http2Rpc), err
}

View File

@@ -26,6 +26,10 @@ type FakeNetworkingV1 struct {
*testing.Fake
}
func (c *FakeNetworkingV1) Http2Rpcs(namespace string) v1.Http2RpcInterface {
return &FakeHttp2Rpcs{c, namespace}
}
func (c *FakeNetworkingV1) McpBridges(namespace string) v1.McpBridgeInterface {
return &FakeMcpBridges{c, namespace}
}

View File

@@ -16,4 +16,6 @@
package v1
type Http2RpcExpansion interface{}
type McpBridgeExpansion interface{}

View File

@@ -0,0 +1,193 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by client-gen. DO NOT EDIT.
package v1
import (
"context"
"time"
v1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
scheme "github.com/alibaba/higress/client/pkg/clientset/versioned/scheme"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// Http2RpcsGetter has a method to return a Http2RpcInterface.
// A group's client should implement this interface.
type Http2RpcsGetter interface {
Http2Rpcs(namespace string) Http2RpcInterface
}
// Http2RpcInterface has methods to work with Http2Rpc resources.
type Http2RpcInterface interface {
Create(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.CreateOptions) (*v1.Http2Rpc, error)
Update(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.UpdateOptions) (*v1.Http2Rpc, error)
UpdateStatus(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.UpdateOptions) (*v1.Http2Rpc, error)
Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Http2Rpc, error)
List(ctx context.Context, opts metav1.ListOptions) (*v1.Http2RpcList, error)
Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Http2Rpc, err error)
Http2RpcExpansion
}
// http2Rpcs implements Http2RpcInterface
type http2Rpcs struct {
client rest.Interface
ns string
}
// newHttp2Rpcs returns a Http2Rpcs
func newHttp2Rpcs(c *NetworkingV1Client, namespace string) *http2Rpcs {
return &http2Rpcs{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the http2Rpc, and returns the corresponding http2Rpc object, and an error if there is any.
func (c *http2Rpcs) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Get().
Namespace(c.ns).
Resource("http2rpcs").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
// List takes label and field selectors, and returns the list of Http2Rpcs that match those selectors.
func (c *http2Rpcs) List(ctx context.Context, opts metav1.ListOptions) (result *v1.Http2RpcList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.Http2RpcList{}
err = c.client.Get().
Namespace(c.ns).
Resource("http2rpcs").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested http2Rpcs.
func (c *http2Rpcs) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("http2rpcs").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Watch(ctx)
}
// Create takes the representation of a http2Rpc and creates it. Returns the server's representation of the http2Rpc, and an error, if there is any.
func (c *http2Rpcs) Create(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.CreateOptions) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Post().
Namespace(c.ns).
Resource("http2rpcs").
VersionedParams(&opts, scheme.ParameterCodec).
Body(http2Rpc).
Do(ctx).
Into(result)
return
}
// Update takes the representation of a http2Rpc and updates it. Returns the server's representation of the http2Rpc, and an error, if there is any.
func (c *http2Rpcs) Update(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.UpdateOptions) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Put().
Namespace(c.ns).
Resource("http2rpcs").
Name(http2Rpc.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(http2Rpc).
Do(ctx).
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *http2Rpcs) UpdateStatus(ctx context.Context, http2Rpc *v1.Http2Rpc, opts metav1.UpdateOptions) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Put().
Namespace(c.ns).
Resource("http2rpcs").
Name(http2Rpc.Name).
SubResource("status").
VersionedParams(&opts, scheme.ParameterCodec).
Body(http2Rpc).
Do(ctx).
Into(result)
return
}
// Delete takes name of the http2Rpc and deletes it. Returns an error if one occurs.
func (c *http2Rpcs) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("http2rpcs").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *http2Rpcs) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error {
var timeout time.Duration
if listOpts.TimeoutSeconds != nil {
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
}
return c.client.Delete().
Namespace(c.ns).
Resource("http2rpcs").
VersionedParams(&listOpts, scheme.ParameterCodec).
Timeout(timeout).
Body(&opts).
Do(ctx).
Error()
}
// Patch applies the patch and returns the patched http2Rpc.
func (c *http2Rpcs) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Http2Rpc, err error) {
result = &v1.Http2Rpc{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("http2rpcs").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}

View File

@@ -24,6 +24,7 @@ import (
type NetworkingV1Interface interface {
RESTClient() rest.Interface
Http2RpcsGetter
McpBridgesGetter
}
@@ -32,6 +33,10 @@ type NetworkingV1Client struct {
restClient rest.Interface
}
func (c *NetworkingV1Client) Http2Rpcs(namespace string) Http2RpcInterface {
return newHttp2Rpcs(c, namespace)
}
func (c *NetworkingV1Client) McpBridges(namespace string) McpBridgeInterface {
return newMcpBridges(c, namespace)
}

View File

@@ -56,6 +56,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Extensions().V1alpha1().WasmPlugins().Informer()}, nil
// Group=networking.higress.io, Version=v1
case v1.SchemeGroupVersion.WithResource("http2rpcs"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1().Http2Rpcs().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("mcpbridges"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Networking().V1().McpBridges().Informer()}, nil

View File

@@ -0,0 +1,88 @@
// 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.
// Code generated by informer-gen. DO NOT EDIT.
package v1
import (
"context"
time "time"
networkingv1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
versioned "github.com/alibaba/higress/client/pkg/clientset/versioned"
internalinterfaces "github.com/alibaba/higress/client/pkg/informers/externalversions/internalinterfaces"
v1 "github.com/alibaba/higress/client/pkg/listers/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// Http2RpcInformer provides access to a shared informer and lister for
// Http2Rpcs.
type Http2RpcInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.Http2RpcLister
}
type http2RpcInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewHttp2RpcInformer constructs a new informer for Http2Rpc type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewHttp2RpcInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredHttp2RpcInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredHttp2RpcInformer constructs a new informer for Http2Rpc type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredHttp2RpcInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.NetworkingV1().Http2Rpcs(namespace).List(context.TODO(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.NetworkingV1().Http2Rpcs(namespace).Watch(context.TODO(), options)
},
},
&networkingv1.Http2Rpc{},
resyncPeriod,
indexers,
)
}
func (f *http2RpcInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredHttp2RpcInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *http2RpcInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&networkingv1.Http2Rpc{}, f.defaultInformer)
}
func (f *http2RpcInformer) Lister() v1.Http2RpcLister {
return v1.NewHttp2RpcLister(f.Informer().GetIndexer())
}

View File

@@ -22,6 +22,8 @@ import (
// Interface provides access to all the informers in this group version.
type Interface interface {
// Http2Rpcs returns a Http2RpcInformer.
Http2Rpcs() Http2RpcInformer
// McpBridges returns a McpBridgeInformer.
McpBridges() McpBridgeInformer
}
@@ -37,6 +39,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// Http2Rpcs returns a Http2RpcInformer.
func (v *version) Http2Rpcs() Http2RpcInformer {
return &http2RpcInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// McpBridges returns a McpBridgeInformer.
func (v *version) McpBridges() McpBridgeInformer {
return &mcpBridgeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@@ -16,6 +16,14 @@
package v1
// Http2RpcListerExpansion allows custom methods to be added to
// Http2RpcLister.
type Http2RpcListerExpansion interface{}
// Http2RpcNamespaceListerExpansion allows custom methods to be added to
// Http2RpcNamespaceLister.
type Http2RpcNamespaceListerExpansion interface{}
// McpBridgeListerExpansion allows custom methods to be added to
// McpBridgeLister.
type McpBridgeListerExpansion interface{}

View File

@@ -0,0 +1,92 @@
// 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.
// Code generated by lister-gen. DO NOT EDIT.
package v1
import (
v1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// Http2RpcLister helps list Http2Rpcs.
type Http2RpcLister interface {
// List lists all Http2Rpcs in the indexer.
List(selector labels.Selector) (ret []*v1.Http2Rpc, err error)
// Http2Rpcs returns an object that can list and get Http2Rpcs.
Http2Rpcs(namespace string) Http2RpcNamespaceLister
Http2RpcListerExpansion
}
// http2RpcLister implements the Http2RpcLister interface.
type http2RpcLister struct {
indexer cache.Indexer
}
// NewHttp2RpcLister returns a new Http2RpcLister.
func NewHttp2RpcLister(indexer cache.Indexer) Http2RpcLister {
return &http2RpcLister{indexer: indexer}
}
// List lists all Http2Rpcs in the indexer.
func (s *http2RpcLister) List(selector labels.Selector) (ret []*v1.Http2Rpc, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1.Http2Rpc))
})
return ret, err
}
// Http2Rpcs returns an object that can list and get Http2Rpcs.
func (s *http2RpcLister) Http2Rpcs(namespace string) Http2RpcNamespaceLister {
return http2RpcNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// Http2RpcNamespaceLister helps list and get Http2Rpcs.
type Http2RpcNamespaceLister interface {
// List lists all Http2Rpcs in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1.Http2Rpc, err error)
// Get retrieves the Http2Rpc from the indexer for a given namespace and name.
Get(name string) (*v1.Http2Rpc, error)
Http2RpcNamespaceListerExpansion
}
// http2RpcNamespaceLister implements the Http2RpcNamespaceLister
// interface.
type http2RpcNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all Http2Rpcs in the indexer for a given namespace.
func (s http2RpcNamespaceLister) List(selector labels.Selector) (ret []*v1.Http2Rpc, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1.Http2Rpc))
})
return ret, err
}
// Get retrieves the Http2Rpc from the indexer for a given namespace and name.
func (s http2RpcNamespaceLister) Get(name string) (*v1.Http2Rpc, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1.Resource("http2rpc"), name)
}
return obj.(*v1.Http2Rpc), nil
}

1
helm/core/.helmignore Normal file
View File

@@ -0,0 +1 @@
crds/customresourcedefinitions.gen_lt1.16.yaml

View File

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

View File

@@ -104,6 +104,88 @@ spec:
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
"helm.sh/resource-policy": keep
name: http2rpcs.networking.higress.io
spec:
group: networking.higress.io
names:
categories:
- higress-io
kind: Http2Rpc
listKind: Http2RpcList
plural: http2rpcs
singular: http2rpc
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
properties:
spec:
oneOf:
- not:
anyOf:
- required:
- dubbo
- required:
- grpc
- required:
- dubbo
- required:
- grpc
properties:
dubbo:
properties:
group:
type: string
methods:
items:
properties:
headersAttach:
type: string
httpMethods:
items:
type: string
type: array
httpPath:
type: string
params:
items:
properties:
paramKey:
type: string
paramSource:
type: string
paramType:
type: string
type: object
type: array
serviceMethod:
type: string
type: object
type: array
service:
type: string
version:
type: string
type: object
grpc:
type: object
type: object
status:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition

View File

@@ -0,0 +1,176 @@
# DO NOT EDIT - Generated by Cue OpenAPI generator based on Istio APIs.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
"helm.sh/resource-policy": keep
name: wasmplugins.extensions.higress.io
spec:
group: extensions.higress.io
names:
categories:
- higress-io
- extensions-higress-io
kind: WasmPlugin
listKind: WasmPluginList
plural: wasmplugins
singular: wasmplugin
scope: Namespaced
additionalPrinterColumns:
- description: 'CreationTimestamp is a timestamp representing the server time
when this object was created. It is not guaranteed to be set in happens-before
order across separate operations. Clients may not set this value. It is represented
in RFC3339 form and is in UTC. Populated by the system. Read-only. Null for
lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
JSONPath: .metadata.creationTimestamp
name: Age
type: date
versions:
- name: v1alpha1
served: true
storage: true
version: v1alpha1
validation:
openAPIV3Schema:
properties:
spec:
properties:
defaultConfig:
type: object
x-kubernetes-preserve-unknown-fields: true
defaultConfigDisable:
type: boolean
imagePullPolicy:
description: The pull behaviour to be applied when fetching an OCI
image.
enum:
- UNSPECIFIED_POLICY
- IfNotPresent
- Always
type: string
imagePullSecret:
description: Credentials to use for OCI image pulling.
type: string
matchRules:
items:
properties:
config:
type: object
x-kubernetes-preserve-unknown-fields: true
configDisable:
type: boolean
domain:
items:
type: string
type: array
ingress:
items:
type: string
type: array
type: object
type: array
phase:
description: Determines where in the filter chain this `WasmPlugin`
is to be injected.
enum:
- UNSPECIFIED_PHASE
- AUTHN
- AUTHZ
- STATS
type: string
pluginConfig:
description: The configuration that will be passed on to the plugin.
type: object
x-kubernetes-preserve-unknown-fields: true
pluginName:
type: string
priority:
description: Determines ordering of `WasmPlugins` in the same `phase`.
nullable: true
type: integer
sha256:
description: SHA256 checksum that will be used to verify Wasm module
or OCI container.
type: string
url:
description: URL of a Wasm module or OCI container.
type: string
verificationKey:
type: string
type: object
status:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
"helm.sh/resource-policy": keep
name: mcpbridges.networking.higress.io
spec:
group: networking.higress.io
names:
categories:
- higress-io
kind: McpBridge
listKind: McpBridgeList
plural: mcpbridges
singular: mcpbridge
scope: Namespaced
versions:
- name: v1
served: true
storage: true
version: v1
validation:
openAPIV3Schema:
properties:
spec:
properties:
registries:
items:
properties:
consulNamespace:
type: string
domain:
type: string
nacosAccessKey:
type: string
nacosAddressServer:
type: string
nacosGroups:
items:
type: string
type: array
nacosNamespace:
type: string
nacosNamespaceId:
type: string
nacosRefreshInterval:
format: int64
type: integer
nacosSecretKey:
type: string
name:
type: string
port:
type: integer
type:
type: string
zkServicesPath:
items:
type: string
type: array
type: object
type: array
type: object
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
subresources:
status: {}
---

View File

@@ -46,6 +46,10 @@ rules:
resources: ["wasmplugins"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.higress.io"]
resources: ["http2rpcs"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "watch", "list", "update", "patch", "create", "delete"]

View File

@@ -100,6 +100,10 @@ spec:
fieldPath: spec.serviceAccountName
- name: KUBECONFIG
value: /var/run/secrets/remote/config
- name: PRIORITIZED_LEADER_ELECTION
value: "false"
- name: INJECT_ENABLED
value: "false"
{{- if .Values.pilot.env }}
{{- range $key, $val := .Values.pilot.env }}
- name: {{ $key }}

View File

@@ -206,6 +206,10 @@ spec:
- mountPath: /etc/istio/custom-bootstrap
name: custom-bootstrap-volume
{{- end }}
{{- if .Values.global.volumeWasmPlugins }}
- mountPath: /opt/plugins
name: local-wasmplugins-volume
{{- end }}
{{- if .Values.gateway.hostNetwork }}
hostNetwork: {{ .Values.gateway.hostNetwork }}
dnsPolicy: ClusterFirstWithHostNet
@@ -274,3 +278,9 @@ spec:
containerName: higress-gateway
divisor: 1m
resource: limits.cpu
{{- if .Values.global.volumeWasmPlugins }}
- name: local-wasmplugins-volume
hostPath:
path: /opt/plugins
type: Directory
{{- end }}

View File

@@ -45,7 +45,7 @@ global:
# Dev builds from prow are on gcr.io
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
# Default tag for Istio images.
tag: 1.0.0
tag: 1.1.0
# Specify image pull policy if default behavior isn't desired.
# Default behavior: latest images will be Always else IfNotPresent.
@@ -369,7 +369,7 @@ gateway:
name: "higress-gateway"
replicas: 2
image: gateway
tag: "1.0.0"
tag: "1.1.0"
# revision declares which revision this gateway is a part of
revision: ""
@@ -457,7 +457,7 @@ controller:
name: "higress-controller"
replicas: 1
image: higress
tag: "1.0.0"
tag: "1.1.0"
env: {}
labels: {}
@@ -547,7 +547,7 @@ pilot:
rollingMaxUnavailable: 25%
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
tag: 1.0.0
tag: 1.1.0
# Can be a full hub/image:tag
image: pilot
@@ -560,7 +560,7 @@ pilot:
memory: 2048Mi
env:
PILOT_SCOPE_GATEWAY_TO_NAMESPACE: "true"
PILOT_SCOPE_GATEWAY_TO_NAMESPACE: "false"
PILOT_ENABLE_METADATA_EXCHANGE: "false"
PILOT_ENABLE_CROSS_CLUSTER_WORKLOAD_ENTRY: "false"
VALIDATION_ENABLED: "false"

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 1.0.0
version: 1.1.0
- name: higress-console
repository: https://higress.io/helm-charts/
version: 1.0.0
digest: sha256:fb0f1b6816df5f5ac6888a93fb148b55b669affc9ac99336dd1ac818b8f84ace
generated: "2023-05-22T17:42:02.506864+08:00"
version: 1.1.0
digest: sha256:ab013ed8a315faf016e4114f2315af3449df0a6aa62bb0b8b83704d346109722
generated: "2023-07-10T20:56:37.100196+08:00"

View File

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

View File

@@ -140,7 +140,7 @@ index 25479439b7..bd34442260 100755
# This script builds and version stamps the output
+export GOPROXY="https://proxy.golang.com.cn,direct"
+export GOPROXY=${GOPROXY:-"https://proxy.golang.com.cn,direct"}
+
VERBOSE=${VERBOSE:-"0"}
V=""
@@ -215,10 +215,11 @@ index 271fe77a2d..c9e625efdc 100755
--init \
--sig-proxy=true \
${DOCKER_SOCKET_MOUNT:--v /var/run/docker.sock:/var/run/docker.sock} \
@@ -55,7 +59,15 @@ read -ra DOCKER_RUN_OPTIONS <<< "${DOCKER_RUN_OPTIONS:-}"
@@ -55,7 +59,16 @@ read -ra DOCKER_RUN_OPTIONS <<< "${DOCKER_RUN_OPTIONS:-}"
--env-file <(env | grep -v ${ENV_BLOCKLIST}) \
-e IN_BUILD_CONTAINER=1 \
-e TZ="${TIMEZONE:-$TZ}" \
+ -e GOPROXY="${GOPROXY}" \
+ -e HUB="${HUB}" \
+ -e ENVOY_TAR_PATH="${ENVOY_TAR_PATH}" \
+ --mount "type=bind,source=${MOUNT_PACKAGE_SOURCE},destination=/home/package" \

View File

@@ -0,0 +1,30 @@
diff --color -Naur istio/pilot/pkg/features/pilot.go istio_new/pilot/pkg/features/pilot.go
--- istio/pilot/pkg/features/pilot.go 2023-06-18 20:13:57.715044832 +0800
+++ istio_new/pilot/pkg/features/pilot.go 2023-06-18 20:11:40.310406690 +0800
@@ -359,6 +359,9 @@
EnableUnsafeAdminEndpoints = env.RegisterBoolVar("UNSAFE_ENABLE_ADMIN_ENDPOINTS", false,
"If this is set to true, dangerous admin endpoints will be exposed on the debug interface. Not recommended for production.").Get()
+ DebugAuth = env.RegisterBoolVar("DEBUG_AUTH", true,
+ "If this is set to false, the debug interface will allow all anonymous request from any remote host, which is not recommended for production").Get()
+
XDSAuth = env.RegisterBoolVar("XDS_AUTH", true,
"If true, will authenticate XDS clients.").Get()
diff --color -Naur istio/pilot/pkg/xds/debug.go istio_new/pilot/pkg/xds/debug.go
--- istio/pilot/pkg/xds/debug.go 2023-06-18 20:13:57.695044739 +0800
+++ istio_new/pilot/pkg/xds/debug.go 2023-06-18 20:11:40.286406579 +0800
@@ -218,8 +218,12 @@
if internalMux != nil {
internalMux.HandleFunc(path, handler)
}
+ handlerFunc := http.HandlerFunc(handler)
+ if features.DebugAuth {
+ handlerFunc = s.allowAuthenticatedOrLocalhost(handlerFunc)
+ }
// Add handler with auth; this is expose on an HTTP server
- mux.HandleFunc(path, s.allowAuthenticatedOrLocalhost(http.HandlerFunc(handler)))
+ mux.HandleFunc(path, handlerFunc)
}
func (s *DiscoveryServer) allowAuthenticatedOrLocalhost(next http.Handler) http.HandlerFunc {

View File

@@ -0,0 +1,15 @@
diff -Naur istio/pilot/pkg/xds/debug.go istio-new/pilot/pkg/xds/debug.go
--- istio/pilot/pkg/xds/debug.go 2023-06-27 14:08:00.000000000 +0800
+++ istio-new/pilot/pkg/xds/debug.go 2023-06-27 14:07:04.000000000 +0800
@@ -469,6 +469,11 @@
s.Env.IstioConfigStore.Schemas().ForEach(func(schema collection.Schema) bool {
cfg, _ := s.Env.IstioConfigStore.List(schema.Resource().GroupVersionKind(), "")
// Added by ingress
+ copied := make([]config.Config, len(cfg))
+ for i := range copied {
+ copied[i] = cfg[i].DeepCopy()
+ }
+ cfg = copied
switch schema.Resource().GroupVersionKind().String() {
case gvk.Gateway.String():
cfg = model.GatewayFilter(cfg)

View File

@@ -15,6 +15,7 @@
package config
import (
"bytes"
"encoding/json"
"errors"
"fmt"
@@ -25,6 +26,7 @@ import (
wasm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/wasm/v3"
httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/wasm/v3"
"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/types"
"github.com/golang/protobuf/ptypes/wrappers"
"google.golang.org/protobuf/types/known/anypb"
@@ -42,10 +44,13 @@ import (
"k8s.io/client-go/tools/cache"
higressext "github.com/alibaba/higress/api/extensions/v1alpha1"
higressv1 "github.com/alibaba/higress/api/networking/v1"
extlisterv1 "github.com/alibaba/higress/client/pkg/listers/extensions/v1alpha1"
netlisterv1 "github.com/alibaba/higress/client/pkg/listers/networking/v1"
"github.com/alibaba/higress/pkg/ingress/kube/annotations"
"github.com/alibaba/higress/pkg/ingress/kube/common"
"github.com/alibaba/higress/pkg/ingress/kube/configmap"
"github.com/alibaba/higress/pkg/ingress/kube/http2rpc"
"github.com/alibaba/higress/pkg/ingress/kube/ingress"
"github.com/alibaba/higress/pkg/ingress/kube/ingressv1"
"github.com/alibaba/higress/pkg/ingress/kube/mcpbridge"
@@ -54,12 +59,30 @@ import (
"github.com/alibaba/higress/pkg/ingress/kube/wasmplugin"
. "github.com/alibaba/higress/pkg/ingress/log"
"github.com/alibaba/higress/pkg/kube"
"github.com/alibaba/higress/registry/memory"
"github.com/alibaba/higress/registry/reconcile"
)
var (
_ model.ConfigStoreCache = &IngressConfig{}
_ model.IngressStore = &IngressConfig{}
_ model.ConfigStoreCache = &IngressConfig{}
_ model.IngressStore = &IngressConfig{}
Http2RpcMethodMap = func() map[string]string {
return map[string]string{
"GET": "ALL_GET",
"POST": "ALL_POST",
"PUT": "ALL_PUT",
"DELETE": "ALL_DELETE",
"PATCH": "ALL_PATCH",
}
}
Http2RpcParamSourceMap = func() map[string]string {
return map[string]string{
"QUERY": "ALL_QUERY_PARAMETER",
"HEADER": "ALL_HEADER",
"PATH": "ALL_PATH",
"BODY": "ALL_BODY",
}
}
)
type IngressConfig struct {
@@ -98,6 +121,14 @@ type IngressConfig struct {
wasmPlugins map[string]*extensions.WasmPlugin
http2rpcController http2rpc.Http2RpcController
http2rpcLister netlisterv1.Http2RpcLister
http2rpcs map[string]*higressv1.Http2Rpc
configmapMgr *configmap.ConfigmapMgr
XDSUpdater model.XDSUpdater
annotationHandler annotations.AnnotationHandler
@@ -125,6 +156,7 @@ func NewIngressConfig(localKubeClient kube.Client, XDSUpdater model.XDSUpdater,
namespace: namespace,
mcpbridgeReconciled: true,
wasmPlugins: make(map[string]*extensions.WasmPlugin),
http2rpcs: make(map[string]*higressv1.Http2Rpc),
}
mcpbridgeController := mcpbridge.NewController(localKubeClient, clusterId)
mcpbridgeController.AddEventHandler(config.AddOrUpdateMcpBridge, config.DeleteMcpBridge)
@@ -135,6 +167,15 @@ func NewIngressConfig(localKubeClient kube.Client, XDSUpdater model.XDSUpdater,
wasmPluginController.AddEventHandler(config.AddOrUpdateWasmPlugin, config.DeleteWasmPlugin)
config.wasmPluginController = wasmPluginController
config.wasmPluginLister = wasmPluginController.Lister()
http2rpcController := http2rpc.NewController(localKubeClient, clusterId)
http2rpcController.AddEventHandler(config.AddOrUpdateHttp2Rpc, config.DeleteHttp2Rpc)
config.http2rpcController = http2rpcController
config.http2rpcLister = http2rpcController.Lister()
higressConfigController := configmap.NewController(localKubeClient, clusterId, namespace)
config.configmapMgr = configmap.NewConfigmapMgr(XDSUpdater, namespace, higressConfigController, higressConfigController.Lister())
return config
}
@@ -207,8 +248,24 @@ func (m *IngressConfig) List(typ config.GroupVersionKind, namespace string) ([]c
if typ == gvk.EnvoyFilter {
m.mutex.RLock()
defer m.mutex.RUnlock()
IngressLog.Infof("resource type %s, configs number %d", typ, len(m.cachedEnvoyFilters))
return m.cachedEnvoyFilters, nil
var envoyFilters []config.Config
// Build configmap envoy filters
configmapEnvoyFilters, err := m.configmapMgr.ConstructEnvoyFilters()
if err != nil {
IngressLog.Errorf("Construct configmap EnvoyFilters error %v", err)
} else {
for _, envoyFilter := range configmapEnvoyFilters {
envoyFilters = append(envoyFilters, *envoyFilter)
}
IngressLog.Infof("Append %d configmap EnvoyFilters", len(configmapEnvoyFilters))
}
if len(envoyFilters) == 0 {
IngressLog.Infof("resource type %s, configs number %d", typ, len(m.cachedEnvoyFilters))
return m.cachedEnvoyFilters, nil
}
envoyFilters = append(envoyFilters, m.cachedEnvoyFilters...)
IngressLog.Infof("resource type %s, configs number %d", typ, len(envoyFilters))
return envoyFilters, nil
}
var configs []config.Config
@@ -458,6 +515,18 @@ func (m *IngressConfig) convertEnvoyFilter(convertOptions *common.ConvertOptions
continue
}
http2rpc := route.WrapperConfig.AnnotationsConfig.Http2Rpc
if http2rpc != nil {
IngressLog.Infof("Found http2rpc for name %s", http2rpc.Name)
envoyFilter, err := m.constructHttp2RpcEnvoyFilter(http2rpc, route, m.namespace)
if err != nil {
IngressLog.Errorf("Construct http2rpc EnvoyFilter error %v", err)
} else {
IngressLog.Infof("Append http2rpc EnvoyFilter for name %s", http2rpc.Name)
envoyFilters = append(envoyFilters, *envoyFilter)
}
}
auth := route.WrapperConfig.AnnotationsConfig.Auth
if auth == nil {
continue
@@ -521,6 +590,7 @@ func (m *IngressConfig) convertServiceEntry([]common.WrapperConfig) []config.Con
return nil
}
serviceEntries := m.RegistryReconciler.GetAllServiceEntryWrapper()
IngressLog.Infof("Found http2rpc serviceEntries %s", serviceEntries)
out := make([]config.Config, 0, len(serviceEntries))
for _, se := range serviceEntries {
out = append(out, config.Config{
@@ -912,6 +982,38 @@ func (m *IngressConfig) DeleteMcpBridge(clusterNamespacedName util.ClusterNamesp
}
}
func (m *IngressConfig) AddOrUpdateHttp2Rpc(clusterNamespacedName util.ClusterNamespacedName) {
if clusterNamespacedName.Namespace != m.namespace {
return
}
http2rpc, err := m.http2rpcLister.Http2Rpcs(clusterNamespacedName.Namespace).Get(clusterNamespacedName.Name)
if err != nil {
IngressLog.Errorf("http2rpc is not found, namespace:%s, name:%s",
clusterNamespacedName.Namespace, clusterNamespacedName.Name)
return
}
m.mutex.Lock()
m.http2rpcs[clusterNamespacedName.Name] = &http2rpc.Spec
m.mutex.Unlock()
IngressLog.Infof("AddOrUpdateHttp2Rpc http2rpc ingress name %s", clusterNamespacedName.Name)
}
func (m *IngressConfig) DeleteHttp2Rpc(clusterNamespacedName util.ClusterNamespacedName) {
if clusterNamespacedName.Namespace != m.namespace {
return
}
var hit bool
m.mutex.Lock()
if _, ok := m.http2rpcs[clusterNamespacedName.Name]; ok {
delete(m.http2rpcs, clusterNamespacedName.Name)
hit = true
}
m.mutex.Unlock()
if hit {
IngressLog.Debugf("Http2Rpc triggerd deleted %s", clusterNamespacedName.Name)
}
}
func (m *IngressConfig) ReflectSecretChanges(clusterNamespacedName util.ClusterNamespacedName) {
var hit bool
m.mutex.RLock()
@@ -996,6 +1098,188 @@ func (m *IngressConfig) applyCanaryIngresses(convertOptions *common.ConvertOptio
}
}
func (m *IngressConfig) constructHttp2RpcEnvoyFilter(http2rpcConfig *annotations.Http2RpcConfig, route *common.WrapperHTTPRoute, namespace string) (*config.Config, error) {
mappings := m.http2rpcs
IngressLog.Infof("Found http2rpc mappings %v", mappings)
if _, exist := mappings[http2rpcConfig.Name]; !exist {
IngressLog.Errorf("Http2RpcConfig name %s, not found Http2Rpc CRD", http2rpcConfig.Name)
return nil, errors.New("invalid http2rpcConfig has no useable http2rpc")
}
http2rpcCRD := mappings[http2rpcConfig.Name]
if http2rpcCRD.GetDubbo() == nil {
IngressLog.Errorf("Http2RpcConfig name %s, only support Http2Rpc CRD Dubbo Service type", http2rpcConfig.Name)
return nil, errors.New("invalid http2rpcConfig has no useable http2rpc")
}
httpRoute := route.HTTPRoute
httpRouteDestination := httpRoute.Route[0]
typeStruct, err := m.constructHttp2RpcMethods(http2rpcCRD.GetDubbo())
if err != nil {
return nil, errors.New(err.Error())
}
return &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: common.CreateConvertedName(constants.IstioIngressGatewayName, http2rpcConfig.Name),
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.router",
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE,
Value: buildPatchStruct(`{
"name":"envoy.filters.http.http_dubbo_transcoder",
"typed_config":{
"@type":"type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url":"type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder"
}
}`),
},
},
{
ApplyTo: networking.EnvoyFilter_HTTP_ROUTE,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{
RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{
Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{
Route: &networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch{
Name: httpRoute.Name,
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: typeStruct,
},
},
{
ApplyTo: networking.EnvoyFilter_CLUSTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{
Cluster: &networking.EnvoyFilter_ClusterMatch{
Service: httpRouteDestination.Destination.Host,
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: buildPatchStruct(`{
"upstream_config": {
"name":"envoy.upstreams.http.dubbo_tcp",
"typed_config":{
"@type":"type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url":"type.googleapis.com/envoy.extensions.upstreams.http.dubbo_tcp.v3.DubboTcpConnectionPoolProto"
}
}
}`),
},
},
},
},
}, nil
}
func (m *IngressConfig) constructHttp2RpcMethods(dubbo *higressv1.DubboService) (*types.Struct, error) {
httpRouterTemplate := `{
"route": {
"upgrade_configs": [
{
"connect_config": {
"allow_post": true
},
"upgrade_type": "CONNECT"
}
]
},
"typed_per_filter_config": {
"envoy.filters.http.http_dubbo_transcoder": {
"@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
"type_url": "type.googleapis.com/envoy.extensions.filters.http.http_dubbo_transcoder.v3.HttpDubboTranscoder",
"value": {
"request_validation_options": {
"reject_unknown_method": true,
"reject_unknown_query_parameters": true
},
"services_mapping": %s,
"url_unescape_spec": "ALL_CHARACTERS_EXCEPT_RESERVED"
}
}
}
}`
var methods []interface{}
for _, serviceMethod := range dubbo.GetMethods() {
var method = make(map[string]interface{})
method["name"] = serviceMethod.GetServiceMethod()
var params []interface{}
for _, methodParam := range serviceMethod.GetParams() {
var param = make(map[string]interface{})
param["extract_key"] = methodParam.GetParamKey()
param["extract_key_spec"] = Http2RpcParamSourceMap()[methodParam.GetParamSource()]
param["mapping_type"] = methodParam.GetParamType()
params = append(params, param)
}
method["parameter_mapping"] = params
var path_matcher = make(map[string]interface{})
path_matcher["match_http_method_spec"] = Http2RpcMethodMap()[serviceMethod.HttpMethods[0]]
path_matcher["match_pattern"] = serviceMethod.GetHttpPath()
method["path_matcher"] = path_matcher
var passthrough_setting = make(map[string]interface{})
var headersAttach = serviceMethod.GetHeadersAttach()
if headersAttach == "" {
passthrough_setting["passthrough_all_headers"] = false
} else if headersAttach == "*" {
passthrough_setting["passthrough_all_headers"] = true
} else {
passthrough_setting["passthrough_headers"] = headersAttach
}
method["passthrough_setting"] = passthrough_setting
methods = append(methods, method)
}
var serviceMapping = make(map[string]interface{})
var dubboServiceGroup = dubbo.GetGroup()
if dubboServiceGroup != "" {
serviceMapping["group"] = dubboServiceGroup
}
serviceMapping["name"] = dubbo.GetService()
serviceMapping["version"] = dubbo.GetVersion()
serviceMapping["method_mapping"] = methods
strBuffer := new(bytes.Buffer)
serviceMappingJsonStr, _ := json.Marshal(serviceMapping)
fmt.Fprintf(strBuffer, httpRouterTemplate, string(serviceMappingJsonStr))
IngressLog.Infof("Found http2rpc buildHttp2RpcMethods %s", strBuffer.String())
result := buildPatchStruct(strBuffer.String())
return result, nil
}
func buildPatchStruct(config string) *types.Struct {
val := &types.Struct{}
_ = jsonpb.Unmarshal(strings.NewReader(config), val)
return val
}
func constructBasicAuthEnvoyFilter(rules *common.BasicAuthRules, namespace string) (*config.Config, error) {
rulesStr, err := json.Marshal(rules)
if err != nil {
@@ -1079,9 +1363,35 @@ func constructBasicAuthEnvoyFilter(rules *common.BasicAuthRules, namespace strin
}, nil
}
func QueryByName(serviceEntries []*memory.ServiceEntryWrapper, serviceName string) (*memory.ServiceEntryWrapper, error) {
IngressLog.Infof("Found http2rpc serviceEntries %s", serviceEntries)
for _, se := range serviceEntries {
if se.ServiceName == serviceName {
return se, nil
}
}
return nil, fmt.Errorf("can't find ServiceEntry by serviceName:%v", serviceName)
}
func QueryRpcServiceVersion(serviceEntry *memory.ServiceEntryWrapper, serviceName string) (string, error) {
IngressLog.Infof("Found http2rpc serviceEntry %s", serviceEntry)
IngressLog.Infof("Found http2rpc ServiceEntry %s", serviceEntry.ServiceEntry)
IngressLog.Infof("Found http2rpc WorkloadSelector %s", serviceEntry.ServiceEntry.WorkloadSelector)
IngressLog.Infof("Found http2rpc Labels %s", serviceEntry.ServiceEntry.WorkloadSelector.Labels)
labels := (*serviceEntry).ServiceEntry.WorkloadSelector.Labels
for key, value := range labels {
if key == "version" {
return value, nil
}
}
return "", fmt.Errorf("can't get RpcServiceVersion for serviceName:%v", serviceName)
}
func (m *IngressConfig) Run(stop <-chan struct{}) {
go m.mcpbridgeController.Run(stop)
go m.wasmPluginController.Run(stop)
go m.http2rpcController.Run(stop)
go m.configmapMgr.HigressConfigController.Run(stop)
}
func (m *IngressConfig) HasSynced() bool {
@@ -1098,6 +1408,12 @@ func (m *IngressConfig) HasSynced() bool {
if !m.wasmPluginController.HasSynced() {
return false
}
if !m.http2rpcController.HasSynced() {
return false
}
if !m.configmapMgr.HigressConfigController.HasSynced() {
return false
}
IngressLog.Info("Ingress config controller synced.")
return true
}

View File

@@ -69,6 +69,8 @@ type Ingress struct {
Match *MatchConfig
HeaderControl *HeaderControlConfig
Http2Rpc *Http2RpcConfig
}
func (i *Ingress) NeedRegexMatch() bool {
@@ -76,7 +78,15 @@ func (i *Ingress) NeedRegexMatch() bool {
return false
}
return i.Rewrite.RewriteTarget != "" || i.Rewrite.UseRegex
return i.Rewrite.RewriteTarget != "" || i.IsPrefixRegexMatch() || i.IsFullPathRegexMatch()
}
func (i *Ingress) IsPrefixRegexMatch() bool {
return i.Rewrite.UseRegex
}
func (i *Ingress) IsFullPathRegexMatch() bool {
return i.Rewrite.FullPathRegex
}
func (i *Ingress) IsCanary() bool {
@@ -141,6 +151,7 @@ func NewAnnotationHandlerManager() AnnotationHandler {
ignoreCaseMatching{},
match{},
headerControl{},
http2rpc{},
},
gatewayHandlers: []GatewayHandler{
downstreamTLS{},

View File

@@ -0,0 +1,53 @@
// Copyright (c) 2023 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotations
import (
. "github.com/alibaba/higress/pkg/ingress/log"
)
const (
http2rpcKey = "http2rpc-name"
rpcDestinationName = "rpc-destination-name"
)
// help to conform http2rpc implements method of Parse
var _ Parser = http2rpc{}
type Http2RpcConfig struct {
Name string
}
type http2rpc struct{}
func (a http2rpc) Parse(annotations Annotations, config *Ingress, _ *GlobalContext) error {
if !needHttp2RpcConfig(annotations) {
return nil
}
value, err := annotations.ParseStringForHigress(rpcDestinationName)
IngressLog.Infof("Parse http2rpc ingress name %s", value)
if err != nil {
IngressLog.Errorf("parse http2rpc error %v within ingress %s/%s", err, config.Namespace, config.Name)
return nil
}
config.Http2Rpc = &Http2RpcConfig{
Name: value,
}
return nil
}
func needHttp2RpcConfig(annotations Annotations) bool {
return annotations.HasHigress(rpcDestinationName)
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package annotations
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestHttp2RpcParse(t *testing.T) {
parser := http2rpc{}
testCases := []struct {
input Annotations
expect *Http2RpcConfig
}{
{
input: Annotations{},
expect: nil,
},
{
input: Annotations{
buildHigressAnnotationKey(rpcDestinationName): "",
},
expect: nil,
},
{
input: Annotations{
buildHigressAnnotationKey(rpcDestinationName): "http-dubbo-alibaba-nacos-example-DemoService",
},
expect: &Http2RpcConfig{
Name: "http-dubbo-alibaba-nacos-example-DemoService",
},
},
}
for _, testCase := range testCases {
t.Run("", func(t *testing.T) {
config := &Ingress{}
_ = parser.Parse(testCase.input, config, nil)
if diff := cmp.Diff(config.Http2Rpc, testCase.expect); diff != "" {
t.Fatalf("TestHttp2RpcParse() mismatch: (-want +got)\n%s", diff)
}
})
}
}

View File

@@ -25,6 +25,7 @@ const (
rewritePath = "rewrite-path"
rewriteTarget = "rewrite-target"
useRegex = "use-regex"
fullPathRegex = "full-path-regex"
upstreamVhost = "upstream-vhost"
re2Regex = "\\$[0-9]"
@@ -38,6 +39,7 @@ var (
type RewriteConfig struct {
RewriteTarget string
UseRegex bool
FullPathRegex bool
RewriteHost string
RewritePath string
}
@@ -52,13 +54,16 @@ func (r rewrite) Parse(annotations Annotations, config *Ingress, _ *GlobalContex
rewriteConfig := &RewriteConfig{}
rewriteConfig.RewriteTarget, _ = annotations.ParseStringASAP(rewriteTarget)
rewriteConfig.UseRegex, _ = annotations.ParseBoolASAP(useRegex)
rewriteConfig.FullPathRegex, _ = annotations.ParseBoolForHigress(fullPathRegex)
rewriteConfig.RewriteHost, _ = annotations.ParseStringASAP(upstreamVhost)
rewriteConfig.RewritePath, _ = annotations.ParseStringForHigress(rewritePath)
if rewriteConfig.RewritePath == "" && rewriteConfig.RewriteTarget != "" {
// When rewrite target is present and not empty,
// we will enforce regex match on all rules in this ingress.
rewriteConfig.UseRegex = true
if !rewriteConfig.UseRegex && !rewriteConfig.FullPathRegex {
rewriteConfig.UseRegex = true
}
// We should convert nginx regex rule to envoy regex rule.
rewriteConfig.RewriteTarget = convertToRE2(rewriteConfig.RewriteTarget)
@@ -108,12 +113,13 @@ func convertToRE2(target string) string {
func NeedRegexMatch(annotations map[string]string) bool {
target, _ := Annotations(annotations).ParseStringASAP(rewriteTarget)
regex, _ := Annotations(annotations).ParseBoolASAP(useRegex)
useRegex, _ := Annotations(annotations).ParseBoolASAP(useRegex)
fullPathRegex, _ := Annotations(annotations).ParseBoolForHigress(fullPathRegex)
return regex || target != ""
return useRegex || target != "" || fullPathRegex
}
func needRewriteConfig(annotations Annotations) bool {
return annotations.HasASAP(rewriteTarget) || annotations.HasASAP(useRegex) ||
annotations.HasASAP(upstreamVhost) || annotations.HasHigress(rewritePath)
annotations.HasASAP(upstreamVhost) || annotations.HasHigress(rewritePath) || annotations.HasHigress(fullPathRegex)
}

View File

@@ -59,7 +59,11 @@ const (
Prefix PathType = "prefix"
Regex PathType = "regex"
// PrefixRegex :if PathType is PrefixRegex, then the /foo/bar/[A-Z0-9]{3} is actually ^/foo/bar/[A-Z0-9]{3}.*
PrefixRegex PathType = "prefixRegex"
// FullPathRegex :if PathType is FullPathRegex, then the /foo/bar/[A-Z0-9]{3} is actually ^/foo/bar/[A-Z0-9]{3}$
FullPathRegex PathType = "fullPathRegex"
DefaultStatusUpdateInterval = 10 * time.Second

View File

@@ -43,11 +43,11 @@ func TestConstructRouteName(t *testing.T) {
{
input: &WrapperHTTPRoute{
Host: "*.test.com",
OriginPathType: Regex,
OriginPathType: PrefixRegex,
OriginPath: "/test/(.*)/?[0-9]",
HTTPRoute: &networking.HTTPRoute{},
},
expect: "*.test.com-regex-/test/(.*)/?[0-9]",
expect: "*.test.com-prefixRegex-/test/(.*)/?[0-9]",
},
{
input: &WrapperHTTPRoute{
@@ -390,7 +390,7 @@ func TestSortRoutes(t *testing.T) {
AnnotationsConfig: &annotations.Ingress{},
},
Host: "test.com",
OriginPathType: Regex,
OriginPathType: PrefixRegex,
OriginPath: "/d(.*)",
ClusterId: "cluster1",
HTTPRoute: &networking.HTTPRoute{

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package configmap
import (
"encoding/json"
)
type Result int32
const (
ResultNothing Result = iota
ResultReplace
ResultDelete
HigressConfigMapName = "higress-config"
HigressConfigMapKey = "higress"
ModelUpdatedReason = "higress configmap updated"
)
type ItemEventHandler = func(name string)
type HigressConfig struct {
Tracing *Tracing `json:"tracing,omitempty"`
}
func NewDefaultHigressConfig() *HigressConfig {
higressConfig := &HigressConfig{
Tracing: NewDefaultTracing(),
}
return higressConfig
}
func GetHigressConfigString(higressConfig *HigressConfig) string {
bytes, _ := json.Marshal(higressConfig)
return string(bytes)
}

View File

@@ -0,0 +1,202 @@
// 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 configmap
import (
"reflect"
"sync/atomic"
"github.com/alibaba/higress/pkg/ingress/kube/controller"
"github.com/alibaba/higress/pkg/ingress/kube/util"
. "github.com/alibaba/higress/pkg/ingress/log"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/schema/gvk"
kubeclient "istio.io/istio/pkg/kube"
"istio.io/istio/pkg/kube/controllers"
"k8s.io/apimachinery/pkg/types"
listersv1 "k8s.io/client-go/listers/core/v1"
"sigs.k8s.io/yaml"
)
type HigressConfigController controller.Controller[listersv1.ConfigMapNamespaceLister]
func NewController(client kubeclient.Client, clusterId string, namespace string) HigressConfigController {
informer := client.KubeInformer().Core().V1().ConfigMaps().Informer()
return controller.NewCommonController("higressConfig", client.KubeInformer().Core().V1().ConfigMaps().Lister().ConfigMaps(namespace),
informer, GetConfigmap, clusterId)
}
func GetConfigmap(lister listersv1.ConfigMapNamespaceLister, namespacedName types.NamespacedName) (controllers.Object, error) {
return lister.Get(namespacedName.Name)
}
type ItemController interface {
GetName() string
AddOrUpdateHigressConfig(name util.ClusterNamespacedName, old *HigressConfig, new *HigressConfig) error
ValidHigressConfig(higressConfig *HigressConfig) error
ConstructEnvoyFilters() ([]*config.Config, error)
RegisterItemEventHandler(eventHandler ItemEventHandler)
}
type ConfigmapMgr struct {
Namespace string
HigressConfigController HigressConfigController
HigressConfigLister listersv1.ConfigMapNamespaceLister
higressConfig atomic.Value
ItemControllers []ItemController
XDSUpdater model.XDSUpdater
}
func NewConfigmapMgr(XDSUpdater model.XDSUpdater, namespace string, higressConfigController HigressConfigController, higressConfigLister listersv1.ConfigMapNamespaceLister) *ConfigmapMgr {
configmapMgr := &ConfigmapMgr{
XDSUpdater: XDSUpdater,
Namespace: namespace,
HigressConfigController: higressConfigController,
HigressConfigLister: higressConfigLister,
higressConfig: atomic.Value{},
}
configmapMgr.HigressConfigController.AddEventHandler(configmapMgr.AddOrUpdateHigressConfig)
configmapMgr.SetHigressConfig(NewDefaultHigressConfig())
tracingController := NewTracingController(namespace)
configmapMgr.AddItemControllers(tracingController)
configmapMgr.initEventHandlers()
return configmapMgr
}
func (c *ConfigmapMgr) SetHigressConfig(higressConfig *HigressConfig) {
c.higressConfig.Store(higressConfig)
}
func (c *ConfigmapMgr) GetHigressConfig() *HigressConfig {
value := c.higressConfig.Load()
if value != nil {
if higressConfig, ok := value.(*HigressConfig); ok {
return higressConfig
}
}
return nil
}
func (c *ConfigmapMgr) AddItemControllers(controllers ...ItemController) {
c.ItemControllers = append(c.ItemControllers, controllers...)
}
func (c *ConfigmapMgr) AddOrUpdateHigressConfig(name util.ClusterNamespacedName) {
if name.Namespace != c.Namespace || name.Name != HigressConfigMapName {
return
}
IngressLog.Infof("configmapMgr AddOrUpdateHigressConfig")
higressConfigmap, err := c.HigressConfigLister.Get(HigressConfigMapName)
if err != nil {
IngressLog.Errorf("higress-config configmap is not found, namespace:%s, name:%s",
name.Namespace, name.Name)
return
}
if _, ok := higressConfigmap.Data[HigressConfigMapKey]; !ok {
return
}
newHigressConfig := NewDefaultHigressConfig()
if err = yaml.Unmarshal([]byte(higressConfigmap.Data[HigressConfigMapKey]), newHigressConfig); err != nil {
IngressLog.Errorf("data:%s, convert to higress config error, error: %+v", higressConfigmap.Data[HigressConfigMapKey], err)
return
}
for _, itemController := range c.ItemControllers {
if itemErr := itemController.ValidHigressConfig(newHigressConfig); itemErr != nil {
IngressLog.Errorf("configmap %s controller valid higress config error, error: %+v", itemController.GetName(), itemErr)
return
}
}
oldHigressConfig := c.GetHigressConfig()
IngressLog.Infof("configmapMgr oldHigressConfig: %s", GetHigressConfigString(oldHigressConfig))
IngressLog.Infof("configmapMgr newHigressConfig: %s", GetHigressConfigString(newHigressConfig))
result, _ := c.CompareHigressConfig(oldHigressConfig, newHigressConfig)
IngressLog.Infof("configmapMgr CompareHigressConfig reuslt is %d", result)
if result == ResultNothing {
return
}
if result == ResultDelete {
newHigressConfig = NewDefaultHigressConfig()
}
if result == ResultReplace || result == ResultDelete {
// Pass AddOrUpdateHigressConfig to itemControllers
for _, itemController := range c.ItemControllers {
IngressLog.Infof("configmap %s controller AddOrUpdateHigressConfig", itemController.GetName())
if itemErr := itemController.AddOrUpdateHigressConfig(name, oldHigressConfig, newHigressConfig); itemErr != nil {
IngressLog.Errorf("configmap %s controller AddOrUpdateHigressConfig error, error: %+v", itemController.GetName(), itemErr)
}
}
c.SetHigressConfig(newHigressConfig)
IngressLog.Infof("configmapMgr higress config AddOrUpdate success, reuslt is %d", result)
// Call updateConfig
}
}
func (c *ConfigmapMgr) ConstructEnvoyFilters() ([]*config.Config, error) {
configs := make([]*config.Config, 0)
for _, itemController := range c.ItemControllers {
IngressLog.Infof("controller %s ConstructEnvoyFilters", itemController.GetName())
if itemConfigs, err := itemController.ConstructEnvoyFilters(); err != nil {
IngressLog.Errorf("controller %s ConstructEnvoyFilters error, error: %+v", itemController.GetName(), err)
} else {
configs = append(configs, itemConfigs...)
}
}
return configs, nil
}
func (c *ConfigmapMgr) CompareHigressConfig(old *HigressConfig, new *HigressConfig) (Result, error) {
if old == nil || new == nil {
return ResultNothing, nil
}
if !reflect.DeepEqual(old, new) {
return ResultReplace, nil
}
return ResultNothing, nil
}
func (c *ConfigmapMgr) initEventHandlers() error {
itemEventHandler := func(name string) {
c.XDSUpdater.ConfigUpdate(&model.PushRequest{
Full: true,
ConfigsUpdated: map[model.ConfigKey]struct{}{{
Kind: gvk.EnvoyFilter,
Name: name,
Namespace: c.Namespace,
}: {}},
Reason: []model.TriggerReason{ModelUpdatedReason},
})
}
for _, itemController := range c.ItemControllers {
itemController.RegisterItemEventHandler(itemEventHandler)
}
return nil
}

View File

@@ -0,0 +1,408 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package configmap
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"sync/atomic"
"github.com/alibaba/higress/pkg/ingress/kube/util"
. "github.com/alibaba/higress/pkg/ingress/log"
networking "istio.io/api/networking/v1alpha3"
"istio.io/istio/pkg/config"
"istio.io/istio/pkg/config/schema/gvk"
)
const (
higressTracingEnvoyFilterName = "higress-config-tracing"
defaultTimeout = 500
defaultSampling = 100.0
)
type Tracing struct {
// Flag to control trace
Enable bool `json:"enable,omitempty"`
// The percentage of requests (0.0 - 100.0) that will be randomly selected for trace generation,
// if not requested by the client or not forced. Default is 100.0.
Sampling float64 `json:"sampling,omitempty"`
// The timeout for the gRPC request. Default is 500ms
Timeout int32 `json:"timeout,omitempty"`
// The tracer implementation to be used by Envoy.
//
// Types that are assignable to Tracer:
Zipkin *Zipkin `json:"zipkin,omitempty"`
Skywalking *Skywalking `json:"skywalking,omitempty"`
OpenTelemetry *OpenTelemetry `json:"opentelemetry,omitempty"`
}
// Zipkin defines configuration for a Zipkin tracer.
type Zipkin struct {
// Address of the Zipkin service (e.g. _zipkin:9411_).
Service string `json:"service,omitempty"`
Port string `json:"port,omitempty"`
}
// Skywalking Defines configuration for a Skywalking tracer.
type Skywalking struct {
// Address of the Skywalking tracer.
Service string `json:"service,omitempty"`
Port string `json:"port,omitempty"`
// The access token
AccessToken string `json:"access_token,omitempty"`
}
// OpenTelemetry Defines configuration for a OpenTelemetry tracer.
type OpenTelemetry struct {
// Address of OpenTelemetry tracer.
Service string `json:"service,omitempty"`
Port string `json:"port,omitempty"`
}
func validServiceAndPort(service string, port string) bool {
if len(service) == 0 || len(port) == 0 {
return false
}
return true
}
func validTracing(t *Tracing) error {
if t == nil {
return nil
}
if t.Timeout <= 0 {
return errors.New("timeout can not be less than zero")
}
if t.Sampling < 0 || t.Sampling > 100 {
return errors.New("sampling must be in (0.0 - 100.0)")
}
tracerNum := 0
if t.Zipkin != nil {
if validServiceAndPort(t.Zipkin.Service, t.Zipkin.Port) {
tracerNum++
} else {
return errors.New("zipkin service and port can not be empty")
}
}
if t.Skywalking != nil {
if validServiceAndPort(t.Skywalking.Service, t.Skywalking.Port) {
tracerNum++
} else {
return errors.New("skywalking service and port can not be empty")
}
}
if t.OpenTelemetry != nil {
if validServiceAndPort(t.OpenTelemetry.Service, t.OpenTelemetry.Port) {
tracerNum++
} else {
return errors.New("opentelemetry service and port can not be empty")
}
}
if tracerNum != 1 {
return errors.New("only one of skywalkingzipkin and opentelemetry configuration can be set")
}
return nil
}
func compareTracing(old *Tracing, new *Tracing) (Result, error) {
if old == nil && new == nil {
return ResultNothing, nil
}
if new == nil {
return ResultDelete, nil
}
if !reflect.DeepEqual(old, new) {
return ResultReplace, nil
}
return ResultNothing, nil
}
func deepCopyTracing(tracing *Tracing) (*Tracing, error) {
newTracing := NewDefaultTracing()
bytes, err := json.Marshal(tracing)
if err != nil {
return nil, err
}
err = json.Unmarshal(bytes, newTracing)
return newTracing, err
}
func NewDefaultTracing() *Tracing {
tracing := &Tracing{
Enable: false,
Timeout: defaultTimeout,
Sampling: defaultSampling,
}
return tracing
}
type TracingController struct {
Namespace string
tracing atomic.Value
Name string
eventHandler ItemEventHandler
}
func NewTracingController(namespace string) *TracingController {
tracingMgr := &TracingController{
Namespace: namespace,
tracing: atomic.Value{},
Name: "tracing",
}
tracingMgr.SetTracing(NewDefaultTracing())
return tracingMgr
}
func (t *TracingController) SetTracing(tracing *Tracing) {
t.tracing.Store(tracing)
}
func (t *TracingController) GetTracing() *Tracing {
value := t.tracing.Load()
if value != nil {
if tracing, ok := value.(*Tracing); ok {
return tracing
}
}
return nil
}
func (t *TracingController) GetName() string {
return t.Name
}
func (t *TracingController) AddOrUpdateHigressConfig(name util.ClusterNamespacedName, old *HigressConfig, new *HigressConfig) error {
if err := validTracing(new.Tracing); err != nil {
IngressLog.Errorf("data:%+v convert to tracing , error: %+v", new.Tracing, err)
return nil
}
result, _ := compareTracing(old.Tracing, new.Tracing)
switch result {
case ResultReplace:
if newTracing, err := deepCopyTracing(new.Tracing); err != nil {
IngressLog.Infof("tracing deepcopy error:%v", err)
} else {
t.SetTracing(newTracing)
IngressLog.Infof("AddOrUpdate Higress config tracing")
t.eventHandler(higressTracingEnvoyFilterName)
IngressLog.Infof("send event with filter name:%s", higressTracingEnvoyFilterName)
}
case ResultDelete:
t.SetTracing(NewDefaultTracing())
IngressLog.Infof("Delete Higress config tracing")
t.eventHandler(higressTracingEnvoyFilterName)
IngressLog.Infof("send event with filter name:%s", higressTracingEnvoyFilterName)
}
return nil
}
func (t *TracingController) ValidHigressConfig(higressConfig *HigressConfig) error {
if higressConfig == nil {
return nil
}
if higressConfig.Tracing == nil {
return nil
}
return validTracing(higressConfig.Tracing)
}
func (t *TracingController) RegisterItemEventHandler(eventHandler ItemEventHandler) {
t.eventHandler = eventHandler
}
func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) {
configs := make([]*config.Config, 0)
tracing := t.GetTracing()
namespace := t.Namespace
if tracing == nil {
return configs, nil
}
if tracing.Enable == false {
return configs, nil
}
tracingConfig := t.constructTracingTracer(tracing, namespace)
if len(tracingConfig) == 0 {
return configs, nil
}
config := &config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.EnvoyFilter,
Name: higressTracingEnvoyFilterName,
Namespace: namespace,
},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(tracingConfig),
},
},
{
ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{
Listener: &networking.EnvoyFilter_ListenerMatch{
FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{
Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{
Name: "envoy.filters.network.http_connection_manager",
SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{
Name: "envoy.filters.http.router",
},
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(`{
"name":"envoy.filters.http.router",
"typed_config":{
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router",
"start_child_span": true
}
}`),
},
},
},
},
}
configs = append(configs, config)
return configs, nil
}
func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace string) string {
tracingConfig := ""
timeout := float32(tracing.Timeout) / 1000
if tracing.Skywalking != nil {
skywalking := tracing.Skywalking
tracingConfig = fmt.Sprintf(`{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"tracing": {
"provider": {
"name": "envoy.tracers.skywalking",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig",
"client_config": {
"service_name": "higress-gateway.%s",
"backend_token": "%s"
},
"grpc_service": {
"envoy_grpc": {
"cluster_name": "outbound|%s||%s"
},
"timeout": "%.3fs"
}
}
},
"random_sampling": {
"value": %.1f
}
}
}
}`, namespace, skywalking.AccessToken, skywalking.Port, skywalking.Service, timeout, tracing.Sampling)
}
if tracing.Zipkin != nil {
zipkin := tracing.Zipkin
tracingConfig = fmt.Sprintf(`{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"tracing": {
"provider": {
"name": "envoy.tracers.zipkin",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
"collector_cluster": "outbound|%s||%s",
"collector_endpoint": "/api/v2/spans",
"collector_hostname": "higress-gateway",
"collector_endpoint_version": "HTTP_JSON",
"split_spans_for_request": true
}
},
"random_sampling": {
"value": %.1f
}
}
}
}`, zipkin.Port, zipkin.Service, tracing.Sampling)
}
if tracing.OpenTelemetry != nil {
opentelemetry := tracing.OpenTelemetry
tracingConfig = fmt.Sprintf(`{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"tracing": {
"provider": {
"name": "envoy.tracers.opentelemetry",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig",
"service_name": "higress-gateway.%s"
"grpc_service": {
"envoy_grpc": {
"cluster_name": "outbound|%s||%s"
},
"timeout": "%.3fs"
}
}
},
"random_sampling": {
"value": %.1f
}
}
}
}`, namespace, opentelemetry.Port, opentelemetry.Service, timeout, tracing.Sampling)
}
return tracingConfig
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package http2rpc
import (
"istio.io/istio/pkg/kube/controllers"
"k8s.io/apimachinery/pkg/types"
listersv1 "github.com/alibaba/higress/client/pkg/listers/networking/v1"
"github.com/alibaba/higress/pkg/ingress/kube/controller"
kubeclient "github.com/alibaba/higress/pkg/kube"
)
type Http2RpcController controller.Controller[listersv1.Http2RpcLister]
func NewController(client kubeclient.Client, clusterId string) Http2RpcController {
informer := client.HigressInformer().Networking().V1().Http2Rpcs().Informer()
return controller.NewCommonController("http2rpc", client.HigressInformer().Networking().V1().Http2Rpcs().Lister(),
informer, GetHttp2Rpc, clusterId)
}
func GetHttp2Rpc(lister listersv1.Http2RpcLister, namespacedName types.NamespacedName) (controllers.Object, error) {
return lister.Http2Rpcs(namespacedName.Namespace).Get(namespacedName.Name)
}

View File

@@ -293,8 +293,9 @@ func (c *controller) HasSynced() bool {
}
func (c *controller) List() []config.Config {
c.mutex.RLock()
out := make([]config.Config, 0, len(c.ingresses))
c.mutex.RUnlock()
for _, raw := range c.ingressInformer.GetStore().List() {
ing, ok := raw.(*ingress.Ingress)
if !ok {
@@ -534,8 +535,12 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
var pathType common.PathType
originPath := httpPath.Path
if wrapper.AnnotationsConfig.NeedRegexMatch() {
pathType = common.Regex
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
if annotationsConfig.IsPrefixRegexMatch() {
pathType = common.PrefixRegex
} else if annotationsConfig.IsFullPathRegexMatch() {
pathType = common.FullPathRegex
}
} else {
switch *httpPath.PathType {
case ingress.PathTypeExact:
@@ -741,8 +746,12 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
var pathType common.PathType
originPath := httpPath.Path
if wrapper.AnnotationsConfig.NeedRegexMatch() {
pathType = common.Regex
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
if annotationsConfig.IsPrefixRegexMatch() {
pathType = common.PrefixRegex
} else if annotationsConfig.IsFullPathRegexMatch() {
pathType = common.FullPathRegex
}
} else {
switch *httpPath.PathType {
case ingress.PathTypeExact:
@@ -1166,10 +1175,14 @@ func (c *controller) generateHttpMatches(pathType common.PathType, path string,
httpMatch := &networking.HTTPMatchRequest{}
switch pathType {
case common.Regex:
case common.PrefixRegex:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: path + ".*"},
}
case common.FullPathRegex:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: path + "$"},
}
case common.Exact:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: path},

View File

@@ -39,8 +39,7 @@ type statusSyncer struct {
watchedNamespace string
ingressLister ingresslister.IngressLister
ingressClassLister ingresslister.IngressClassLister
ingressLister ingresslister.IngressLister
// search service in the mse vpc
serviceLister listerv1.ServiceLister
}
@@ -48,11 +47,10 @@ type statusSyncer struct {
// newStatusSyncer creates a new instance
func newStatusSyncer(localKubeClient, client kubelib.Client, controller *controller, namespace string) *statusSyncer {
return &statusSyncer{
client: client,
controller: controller,
watchedNamespace: namespace,
ingressLister: client.KubeInformer().Networking().V1beta1().Ingresses().Lister(),
ingressClassLister: client.KubeInformer().Networking().V1beta1().IngressClasses().Lister(),
client: client,
controller: controller,
watchedNamespace: namespace,
ingressLister: client.KubeInformer().Networking().V1beta1().Ingresses().Lister(),
// search service in the mse vpc
serviceLister: localKubeClient.KubeInformer().Core().V1().Services().Lister(),
}

View File

@@ -517,8 +517,12 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
var pathType common.PathType
originPath := httpPath.Path
if wrapper.AnnotationsConfig.NeedRegexMatch() {
pathType = common.Regex
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
if annotationsConfig.IsPrefixRegexMatch() {
pathType = common.PrefixRegex
} else if annotationsConfig.IsFullPathRegexMatch() {
pathType = common.FullPathRegex
}
} else {
switch *httpPath.PathType {
case ingress.PathTypeExact:
@@ -610,10 +614,14 @@ func (c *controller) generateHttpMatches(pathType common.PathType, path string,
httpMatch := &networking.HTTPMatchRequest{}
switch pathType {
case common.Regex:
case common.PrefixRegex:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: path + ".*"},
}
case common.FullPathRegex:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Regex{Regex: path + "$"},
}
case common.Exact:
httpMatch.Uri = &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: path},
@@ -747,8 +755,12 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
var pathType common.PathType
originPath := httpPath.Path
if wrapper.AnnotationsConfig.NeedRegexMatch() {
pathType = common.Regex
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
if annotationsConfig.IsPrefixRegexMatch() {
pathType = common.PrefixRegex
} else if annotationsConfig.IsFullPathRegexMatch() {
pathType = common.FullPathRegex
}
} else {
switch *httpPath.PathType {
case ingress.PathTypeExact:

View File

@@ -40,8 +40,7 @@ type statusSyncer struct {
watchedNamespace string
ingressLister ingresslister.IngressLister
ingressClassLister ingresslister.IngressClassLister
ingressLister ingresslister.IngressLister
// search service in the mse vpc
serviceLister listerv1.ServiceLister
}
@@ -49,11 +48,10 @@ type statusSyncer struct {
// newStatusSyncer creates a new instance
func newStatusSyncer(localKubeClient, client kubelib.Client, controller *controller, namespace string) *statusSyncer {
return &statusSyncer{
client: client,
controller: controller,
watchedNamespace: namespace,
ingressLister: client.KubeInformer().Networking().V1().Ingresses().Lister(),
ingressClassLister: client.KubeInformer().Networking().V1().IngressClasses().Lister(),
client: client,
controller: controller,
watchedNamespace: namespace,
ingressLister: client.KubeInformer().Networking().V1().Ingresses().Lister(),
// search service in the mse vpc
serviceLister: localKubeClient.KubeInformer().Core().V1().Services().Lister(),
}

View File

@@ -15,16 +15,9 @@
package mcpbridge
import (
"time"
"istio.io/istio/pkg/kube/controllers"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/cache"
v1 "github.com/alibaba/higress/client/pkg/apis/networking/v1"
"github.com/alibaba/higress/client/pkg/clientset/versioned"
informersv1 "github.com/alibaba/higress/client/pkg/informers/externalversions/networking/v1"
listersv1 "github.com/alibaba/higress/client/pkg/listers/networking/v1"
"github.com/alibaba/higress/pkg/ingress/kube/controller"
kubeclient "github.com/alibaba/higress/pkg/kube"
@@ -33,11 +26,8 @@ import (
type McpBridgeController controller.Controller[listersv1.McpBridgeLister]
func NewController(client kubeclient.Client, clusterId string) McpBridgeController {
informer := client.HigressInformer().InformerFor(&v1.McpBridge{}, func(k versioned.Interface, resync time.Duration) cache.SharedIndexInformer {
return informersv1.NewMcpBridgeInformer(k, metav1.NamespaceAll, resync,
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
})
return controller.NewCommonController("mcpbridge", listersv1.NewMcpBridgeLister(informer.GetIndexer()),
informer := client.HigressInformer().Networking().V1().McpBridges().Informer()
return controller.NewCommonController("mcpbridge", client.HigressInformer().Networking().V1().McpBridges().Lister(),
informer, GetMcpBridge, clusterId)
}

View File

@@ -82,3 +82,9 @@ func MessageToGoGoStruct(msg proto.Message) (*types.Struct, error) {
func CreateServiceFQDN(namespace, name string) string {
return fmt.Sprintf("%s.%s.svc.%s", name, namespace, DefaultDomainSuffix)
}
func BuildPatchStruct(config string) *types.Struct {
val := &types.Struct{}
_ = jsonpb.Unmarshal(strings.NewReader(config), val)
return val
}

View File

@@ -15,16 +15,9 @@
package wasmplugin
import (
"time"
"istio.io/istio/pkg/kube/controllers"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/cache"
v1 "github.com/alibaba/higress/client/pkg/apis/extensions/v1alpha1"
"github.com/alibaba/higress/client/pkg/clientset/versioned"
informersv1 "github.com/alibaba/higress/client/pkg/informers/externalversions/extensions/v1alpha1"
listersv1 "github.com/alibaba/higress/client/pkg/listers/extensions/v1alpha1"
"github.com/alibaba/higress/pkg/ingress/kube/controller"
kubeclient "github.com/alibaba/higress/pkg/kube"
@@ -33,11 +26,8 @@ import (
type WasmPluginController controller.Controller[listersv1.WasmPluginLister]
func NewController(client kubeclient.Client, clusterId string) WasmPluginController {
informer := client.HigressInformer().InformerFor(&v1.WasmPlugin{}, func(k versioned.Interface, resync time.Duration) cache.SharedIndexInformer {
return informersv1.NewWasmPluginInformer(k, metav1.NamespaceAll, resync,
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
})
return controller.NewCommonController("wasmplugin", listersv1.NewWasmPluginLister(informer.GetIndexer()),
informer := client.HigressInformer().Extensions().V1alpha1().WasmPlugins().Informer()
return controller.NewCommonController("wasmplugin", client.HigressInformer().Extensions().V1alpha1().WasmPlugins().Lister(),
informer, GetWasmPlugin, clusterId)
}

View File

@@ -1,11 +1,12 @@
PLUGIN_NAME ?= hello-world
REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
BUILDER_REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/
REGISTRY ?=
GO_VERSION ?= 1.19
TINYGO_VERSION ?= 0.25.0
ORAS_VERSION ?= 1.0.0
HIGRESS_VERSION ?= 1.0.0-rc
USE_HIGRESS_TINYGO ?= true
BUILDER ?= ${REGISTRY}wasm-go-builder:go${GO_VERSION}-tinygo${TINYGO_VERSION}-oras${ORAS_VERSION}
BUILDER ?= ${BUILDER_REGISTRY}wasm-go-builder:go${GO_VERSION}-tinygo${TINYGO_VERSION}-oras${ORAS_VERSION}
BUILD_TIME := $(shell date "+%Y%m%d-%H%M%S")
COMMIT_ID := $(shell git rev-parse --short HEAD 2>/dev/null)
IMAGE_TAG = $(if $(strip $(PLUGIN_VERSION)),${PLUGIN_VERSION},${BUILD_TIME}-${COMMIT_ID})

View File

@@ -143,3 +143,55 @@ spec:
```
所有规则会按上面配置的顺序一次执行匹配,当有一个规则匹配时,就停止匹配,并选择匹配的配置执行插件逻辑。
## E2E测试
当你完成一个GO语言的插件功能时, 可以同时创建关联的e2e test cases, 并在本地对插件功能完成测试验证。
### step1. 编写 test cases
在目录./test/ingress/conformance下面, 分别添加xxx.yaml文件和xxx.go文件, 比如测试插件request-block
./test/ingress/conformance/request-block.yaml
```
apiVersion: networking.k8s.io/v1
kind: Ingress
...
...
spec:
defaultConfig:
block_urls:
- "swagger.html"
url: file:///opt/plugins/wasm-go/extensions/request-block/plugin.wasm
```
`其中url中extensions后面的'request-block'为插件所在文件夹名称`
./test/ingress/conformance/request-block.go
### step2. 添加 test cases
将上述所写test cases添加到e2e测试列表中,
./test/ingress/conformance/request-block.yaml
```
...
cSuite.Setup(t)
var higressTests []suite.ConformanceTest
if *isWasmPluginTest {
higressTests = []suite.ConformanceTest{
tests.WasmPluginsRequestBlock,
//这里新增你新写的case方法名称
}
} else {
higressTests = []suite.ConformanceTest{
tests.HTTPRouteSimpleSameNamespace,
tests.HTTPRouteHostNameSameNamespace,
...
```
### step3. 编译插件并执行 test cases
考虑到本地构建wasm比较耗时, 我们支持只构建需要测试的插件(同时你也可以临时修改上面第二小步的测试cases列表, 只执行你新写的case)。
```bash
PLUGIN_NAME=request-block make ingress-wasmplugin-test
```

View File

@@ -29,11 +29,11 @@ You can also use `make build-push` to build and push the image at the same time.
### Environmental parameters
| Name | Optional/Required | Default | 含义 |
|---------------|-------------------|--------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| `PLUGIN_NAME` | Optional | hello-world | The name of the plugin to build. |
| `REGISTRY` | Optional | empty | The regitstry address of the generated image, e.g. `example.registry.io/my-name/`. Note that the REGISTRY value should end with /. |
| `IMG` | Optional | If it is empty, it is generated based on the repository address, plugin name, build time, and git commit id. | The generated image tag will override the `REGISTRY` parameter if it is not empty. |
| Name | Optional/Required | Default | meaning |
|---------------|---------------|--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `PLUGIN_NAME` | Optional | hello-world | The name of the plugin to build. |
| `REGISTRY` | Optional | empty | The registry address of the generated image, e.g. `example.registry.io/my-name/`. Note that the REGISTRY value should end with /. |
| `IMG` | Optional | If it is empty, it is generated based on the repository address, plugin name, build time, and git commit id. | The generated image tag will override the `REGISTRY` parameter if it is not empty. |
## Build on local yourself
@@ -138,3 +138,55 @@ spec:
The rules will be matched in the order of configuration. If one match is found, it will stop, and the matching configuration will take effect.
## E2E test
When you complete a GO plug-in function, you can create associated e2e test cases at the same time, and complete the test verification of the plug-in function locally.
### step1. write test cases
In the directory of `./ test/ingress/conformance`, add the xxx.yaml file and xxx.go file. Such as test for `request-block` wasm-plugin,
./test/ingress/conformance/request-block.yaml
```
apiVersion: networking.k8s.io/v1
kind: Ingress
...
...
spec:
defaultConfig:
block_urls:
- "swagger.html"
url: file:///opt/plugins/wasm-go/extensions/request-block/plugin.wasm
```
`Above of the url, the name of after extensions indicates the name of the folder where the plug-in resides.`
./test/ingress/conformance/request-block.go
### step2. add test cases
Add the test cases written above to the e2e test list,
./test/ingress/conformance/request-block.yaml
```
...
cSuite.Setup(t)
var higressTests []suite.ConformanceTest
if *isWasmPluginTest {
higressTests = []suite.ConformanceTest{
tests.WasmPluginsRequestBlock,
//Add your newly written case method name here
}
} else {
higressTests = []suite.ConformanceTest{
tests.HTTPRouteSimpleSameNamespace,
tests.HTTPRouteHostNameSameNamespace,
...
```
### step3. compile and run test cases
Considering that building wasm locally is time-consuming, we support building only the plug-ins that need to be tested (at the same time, you can also temporarily modify the list of test cases in the second small step above, and only execute your newly written cases).
```bash
PLUGIN_NAME=request-block make ingress-wasmplugin-test
```

View File

@@ -0,0 +1,21 @@
# 功能说明
`chatgpt-proxy`插件实现了代理请求AI大语言模型服务的功能。
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| model | string | 选填 | text-davinci-003 | 配置使用的模型模型名称 |
| apiKey | string | 必填 | - | 配置使用的OpenAI API密钥 |
| promptParam | string | 选填 | prompt | 配置prompt的来源字段名称URL参数 |
| chatgptUri | string | 选填 | api.openai.com/v1/completions | 配置调用AI模型服务的URL路径默认值为OPENAI的API调用路径 |
# 配置示例
## 进行OpenAI curie模型的调用
```yaml
apiKey: "xxxxxxxxxxxxxx",
promptParam: "text",
model: "curie"
```

View File

@@ -0,0 +1,15 @@
module chatgpt-proxy
go 1.19
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230629030002-81e467b6242d
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
github.com/tidwall/gjson v1.14.4
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
)

View File

@@ -0,0 +1,22 @@
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230619024848-7d2a05ef1c35 h1:9xBMl8mJ5CGE1ebqtx9s11zrKJHs0559Yf/QWFh0WIQ=
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230619024848-7d2a05ef1c35/go.mod h1:AzSnkuon5c26nIePTiJQIAFsKdhkNdncLcTuahpGtQs=
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230629030002-81e467b6242d h1:PyBnIfssGRMKvBf6wKH11Z1t+X1Z7qegD1pAO60sFrk=
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230629030002-81e467b6242d/go.mod h1:AzSnkuon5c26nIePTiJQIAFsKdhkNdncLcTuahpGtQs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c h1:OCUFXVGixHLfNjg6/QYEhv+jHJ5mRGhpEUVFv9eWPJE=
github.com/tetratelabs/proxy-wasm-go-sdk v0.19.1-0.20220822060051-f9d179a57f8c/go.mod h1:5t/pWFNJ9eMyu/K/Z+OeGhDJ9sN9eCo8fc2pyM/Qjg4=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -0,0 +1,128 @@
package main
import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {
wrapper.SetCtx(
"chatgpt-proxy",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type MyConfig struct {
Model string
ApiKey string
PromptParam string
ChatgptPath string
HumainId string
AIId string
client wrapper.HttpClient
}
func parseConfig(json gjson.Result, config *MyConfig, log wrapper.Log) error {
chatgptUri := json.Get("chatgptUri").String()
var chatgptHost string
if chatgptUri == "" {
config.ChatgptPath = "/v1/completions"
chatgptHost = "api.openai.com"
} else {
cp, err := url.Parse(chatgptUri)
if err != nil {
return err
}
config.ChatgptPath = cp.Path
chatgptHost = cp.Host
}
if config.ChatgptPath == "" {
return errors.New("not found path in chatgptUri")
}
if chatgptHost == "" {
return errors.New("not found host in chatgptUri")
}
config.client = wrapper.NewClusterClient(wrapper.RouteCluster{
Host: chatgptHost,
})
config.Model = json.Get("model").String()
if config.Model == "" {
config.Model = "text-davinci-003"
}
config.ApiKey = json.Get("apiKey").String()
if config.ApiKey == "" {
return errors.New("no apiKey found in config")
}
config.PromptParam = json.Get("promptParam").String()
if config.PromptParam == "" {
config.PromptParam = "prompt"
}
config.HumainId = json.Get("HumainId").String()
if config.HumainId == "" {
config.HumainId = "Humain:"
}
config.AIId = json.Get("AIId").String()
if config.AIId == "" {
config.AIId = "AI:"
}
return nil
}
const bodyTemplate string = `
{
"model":"%s",
"prompt":"%s",
"temperature":0.9,
"max_tokens": 150,
"top_p": 1,
"frequency_penalty": 0.0,
"presence_penalty": 0.6,
"stop": [" %s", " %s"]
}
`
func onHttpRequestHeaders(ctx wrapper.HttpContext, config MyConfig, log wrapper.Log) types.Action {
pairs := strings.SplitN(ctx.Path(), "?", 2)
if len(pairs) < 2 {
proxywasm.SendHttpResponse(400, nil, []byte("1-need prompt param"), -1)
return types.ActionContinue
}
querys, err := url.ParseQuery(pairs[1])
if err != nil {
proxywasm.SendHttpResponse(400, nil, []byte("2-need prompt param"), -1)
return types.ActionContinue
}
var prompt []string
var ok bool
if prompt, ok = querys[config.PromptParam]; !ok || len(prompt) == 0 {
proxywasm.SendHttpResponse(400, nil, []byte("3-need prompt param"), -1)
return types.ActionContinue
}
body := fmt.Sprintf(bodyTemplate, config.Model, prompt[0], config.HumainId, config.AIId)
err = config.client.Post(config.ChatgptPath, [][2]string{
{"Content-Type", "application/json"},
{"Authorization", "Bearer " + config.ApiKey},
}, []byte(body),
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
var headers [][2]string
for key, value := range responseHeaders {
headers = append(headers, [2]string{key, value[0]})
}
proxywasm.SendHttpResponse(uint32(statusCode), headers, responseBody, -1)
}, 10000)
if err != nil {
proxywasm.SendHttpResponse(500, nil, []byte("Internel Error: "+err.Error()), -1)
return types.ActionContinue
}
return types.ActionPause
}

View File

Binary file not shown.

View File

@@ -0,0 +1,230 @@
# 功能说明
`cors` 插件可以为服务端启用 CORSCross-Origin Resource Sharing跨域资源共享的返回 http 响应头。
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|-----------------------|-----------------|-------|---------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| allow_origins | array of string | 选填 | * | 允许跨域访问的 Origin格式为 scheme://host:port示例如 http://example.com:8081。当 allow_credentials 为 false 时,可以使用 * 来表示允许所有 Origin 通过 |
| allow_origin_patterns | array of string | 选填 | - | 允许跨域访问的 Origin 模式匹配, 用 * 匹配域名或者端口, <br/>比如 http://*.example.com -- 匹配域名, http://*.example.com:[8080,9090] -- 匹配域名和指定端口, http://*.example.com:[*] -- 匹配域名和所有端口。单独 * 表示匹配所有域名和端口 |
| allow_methods | array of string | 选填 | GET, PUT, POST, DELETE, PATCH, OPTIONS | 允许跨域访问的 Method比如GETPOST 等。可以使用 * 来表示允许所有 Method。 |
| allow_headers | array of string | 选填 | DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With<br/>If-Modified-Since,Cache-Control,Content-Type,Authorization | 允许跨域访问时请求方携带哪些非 CORS 规范以外的 Header。可以使用 * 来表示允许任意 Header。 |
| expose_headers | array of string | 选填 | - | 允许跨域访问时响应方携带哪些非 CORS 规范以外的 Header。可以使用 * 来表示允许任意 Header。 |
| allow_credentials | bool | 选填 | false | 是否允许跨域访问的请求方携带凭据(如 Cookie 等)。根据 CORS 规范,如果设置该选项为 true在 allow_origins 不能使用 * 替换成使用 allow_origin_patterns * |
| max_age | number | 选填 | 86400秒 | 浏览器缓存 CORS 结果的最大时间,单位为秒。<br/>在这个时间范围内,浏览器会复用上一次的检查结果 |
> 注意
> * allow_credentials 是一个很敏感的选项请谨慎开启。开启之后allow_credentials 和 allow_origins 为 * 不能同时使用,同时设置时, allow_origins 值为 "*" 生效。
> * allow_origins 和 allow_origin_patterns 可以同时设置, 先检查 allow_origins 是否匹配,然后再检查 allow_origin_patterns 是否匹配
> * 非法 CORS 请求, HTTP 状态码返回是 403 返回体内容为 "Invalid CORS request"
# 配置示例
## 允许所有跨域访问, 不允许请求方携带凭据
```yaml
allow_origins:
- '*'
allow_methods:
- '*'
allow_headers:
- '*'
expose_headers:
- '*'
allow_credentials: false
max_age: 7200
```
## 允许所有跨域访问,同时允许请求方携带凭据
```yaml
allow_origin_patterns:
- '*'
allow_methods:
- '*'
allow_headers:
- '*'
expose_headers:
- '*'
allow_credentials: true
max_age: 7200
```
## 允许特定子域,特定方法,特定请求头跨域访问,同时允许请求方携带凭据
```yaml
allow_origin_patterns:
- http://*.example.com
- http://*.example.org:[8080,9090]
allow_methods:
- GET
- PUT
- POST
- DELETE
allow_headers:
- Token
- Content-Type
- Authorization
expose_headers:
- '*'
allow_credentials: true
max_age: 7200
```
# 测试
## 测试配置
```yaml
apiVersion: networking.higress.io/v1
kind: McpBridge
metadata:
name: mcp-cors-httpbin
namespace: higress-system
spec:
registries:
- domain: httpbin.org
name: httpbin
port: 80
type: dns
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/destination: httpbin.dns
higress.io/upstream-vhost: "httpbin.org"
higress.io/backend-protocol: HTTP
name: ingress-cors-httpbin
namespace: higress-system
spec:
ingressClassName: higress
rules:
- host: httpbin.example.com
http:
paths:
- backend:
resource:
apiGroup: networking.higress.io
kind: McpBridge
name: mcp-cors-httpbin
path: /
pathType: Prefix
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: wasm-cors-httpbin
namespace: higress-system
spec:
defaultConfigDisable: true
matchRules:
- config:
allow_origins:
- http://httpbin.example.net
allow_origin_patterns:
- http://*.example.com:[*]
- http://*.example.org:[9090,8080]
allow_methods:
- GET
- POST
- PATCH
allow_headers:
- Content-Type
- Token
- Authorization
expose_headers:
- X-Custom-Header
- X-Env-UTM
allow_credentials: true
max_age: 3600
configDisable: false
ingress:
- ingress-cors-httpbin
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/cors:1.0.0
imagePullPolicy: Always
```
## 请求测试
### 简单请求
```shell
curl -v -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" http://127.0.0.1/anything/get\?foo\=1
< HTTP/1.1 200 OK
> x-cors-version: 1.0.0
> access-control-allow-origin: http://httpbin2.example.org:9090
> access-control-expose-headers: X-Custom-Header,X-Env-UTM
> access-control-allow-credentials: true
```
### 预检请求
```shell
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Content-Type, Token" http://127.0.0.1/anything/get\?foo\=1
< HTTP/1.1 200 OK
< x-cors-version: 1.0.0
< access-control-allow-origin: http://httpbin2.example.org:9090
< access-control-allow-methods: GET,POST,PATCH
< access-control-allow-headers: Content-Type,Token,Authorization
< access-control-expose-headers: X-Custom-Header,X-Env-UTM
< access-control-allow-credentials: true
< access-control-max-age: 3600
< date: Tue, 23 May 2023 11:41:28 GMT
< server: istio-envoy
< content-length: 0
<
* Connection #0 to host 127.0.0.1 left intact
* Closing connection 0
```
### 非法 CORS Origin 预检请求
```shell
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: GET" http://127.0.0.1/anything/get\?foo\=1
HTTP/1.1 403 Forbidden
< content-length: 70
< content-type: text/plain
< x-cors-version: 1.0.0
< date: Tue, 23 May 2023 11:27:01 GMT
< server: istio-envoy
<
* Connection #0 to host 127.0.0.1 left intact
Invalid CORS request
```
### 非法 CORS Method 预检请求
```shell
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: DELETE" http://127.0.0.1/anything/get\?foo\=1
< HTTP/1.1 403 Forbidden
< content-length: 49
< content-type: text/plain
< x-cors-version: 1.0.0
< date: Tue, 23 May 2023 11:28:51 GMT
< server: istio-envoy
<
* Connection #0 to host 127.0.0.1 left intact
Invalid CORS request
```
### 非法 CORS Header 预检请求
```shell
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: GET" -H "Access-Control-Request-Headers: TokenView" http://127.0.0.1/anything/get\?foo\=1
< HTTP/1.1 403 Forbidden
< content-length: 52
< content-type: text/plain
< x-cors-version: 1.0.0
< date: Tue, 23 May 2023 11:31:03 GMT
< server: istio-envoy
<
* Connection #0 to host 127.0.0.1 left intact
Invalid CORS request
```
# 参考文档
- https://www.ruanyifeng.com/blog/2016/04/cors.html
- https://fetch.spec.whatwg.org/#http-cors-protocol

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,457 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"errors"
"fmt"
"net/url"
"regexp"
"strings"
)
const (
defaultMatchAll = "*"
defaultAllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS"
defaultAllAllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS, HEAD, TRACE, CONNECT"
defaultAllowHeaders = "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With," +
"If-Modified-Since,Cache-Control,Content-Type,Authorization"
defaultMaxAge = 86400
protocolHttpName = "http"
protocolHttpPort = "80"
protocolHttpsName = "https"
protocolHttpsPort = "443"
HeaderPluginDebug = "X-Cors-Version"
HeaderPluginTrace = "X-Cors-Trace"
HeaderOrigin = "Origin"
HttpMethodOptions = "OPTIONS"
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
HeaderControlRequestMethod = "Access-Control-Request-Method"
HeaderControlRequestHeaders = "Access-Control-Request-Headers"
HttpContextKey = "CORS"
)
var portsRegex = regexp.MustCompile(`(.*):\[(\*|\d+(,\d+)*)]`)
type OriginPattern struct {
declaredPattern string
pattern *regexp.Regexp
patternValue string
}
func newOriginPatternFromString(declaredPattern string) OriginPattern {
declaredPattern = strings.ToLower(strings.TrimSuffix(declaredPattern, "/"))
matches := portsRegex.FindAllStringSubmatch(declaredPattern, -1)
portList := ""
patternValue := declaredPattern
if len(matches) > 0 {
patternValue = matches[0][1]
portList = matches[0][2]
}
patternValue = "\\Q" + patternValue + "\\E"
patternValue = strings.ReplaceAll(patternValue, "*", "\\E.*\\Q")
if len(portList) > 0 {
if portList == defaultMatchAll {
patternValue += "(:\\d+)?"
} else {
patternValue += ":(" + strings.ReplaceAll(portList, ",", "|") + ")"
}
}
return OriginPattern{
declaredPattern: declaredPattern,
patternValue: patternValue,
pattern: regexp.MustCompile(patternValue),
}
}
type CorsConfig struct {
// allowOrigins A list of origins for which cross-origin requests are allowed.
// Be a specific domain, e.g. "https://example.com", or the CORS defined special value "*" for all origins.
// Keep in mind however that the CORS spec does not allow "*" when allowCredentials is set to true, using allowOriginPatterns instead
// By default, it is set to "*" when allowOriginPatterns is not set too.
allowOrigins []string
// allowOriginPatterns A list of origin patterns for which cross-origin requests are allowed
// origins patterns with "*" anywhere in the host name in addition to port
// lists Examples:
// https://*.example.com -- domains ending with example.com
// https://*.example.com:[8080,9090] -- domains ending with example.com on port 8080 or port 9090
// https://*.example.com:[*] -- domains ending with example.com on any port, including the default port
// The special value "*" allows all origins
// By default, it is not set.
allowOriginPatterns []OriginPattern
// allowMethods A list of method for which cross-origin requests are allowed
// The special value "*" allows all methods.
// By default, it is set to "GET, PUT, POST, DELETE, PATCH, OPTIONS".
allowMethods []string
// allowHeaders A list of headers that a pre-flight request can list as allowed
// The special value "*" allows actual requests to send any header
// By default, it is set to "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
allowHeaders []string
// exposeHeaders A list of response headers an actual response might have and can be exposed.
// The special value "*" allows all headers to be exposed for non-credentialed requests.
// By default, it is not set
exposeHeaders []string
// allowCredentials Whether user credentials are supported.
// By default, it is not set (i.e. user credentials are not supported).
allowCredentials bool
// maxAge Configure how long, in seconds, the response from a pre-flight request can be cached by clients.
// By default, it is set to 86400 seconds.
maxAge int
}
type HttpCorsContext struct {
IsValid bool
ValidReason string
IsPreFlight bool
IsCorsRequest bool
AllowOrigin string
AllowMethods string
AllowHeaders string
ExposeHeaders string
AllowCredentials bool
MaxAge int
}
func (c *CorsConfig) GetVersion() string {
return "1.0.0"
}
func (c *CorsConfig) FillDefaultValues() {
if len(c.allowOrigins) == 0 && len(c.allowOriginPatterns) == 0 && c.allowCredentials == false {
c.allowOrigins = []string{defaultMatchAll}
}
if len(c.allowHeaders) == 0 {
c.allowHeaders = []string{defaultAllowHeaders}
}
if len(c.allowMethods) == 0 {
c.allowMethods = strings.Split(defaultAllowMethods, "")
}
if c.maxAge == 0 {
c.maxAge = defaultMaxAge
}
}
func (c *CorsConfig) AddAllowOrigin(origin string) error {
origin = strings.TrimSpace(origin)
if len(origin) == 0 {
return nil
}
if origin == defaultMatchAll {
if c.allowCredentials == true {
return errors.New("can't set origin to * when allowCredentials is true, use AllowOriginPatterns instead")
}
c.allowOrigins = []string{defaultMatchAll}
return nil
}
c.allowOrigins = append(c.allowOrigins, strings.TrimSuffix(origin, "/"))
return nil
}
func (c *CorsConfig) AddAllowHeader(header string) {
header = strings.TrimSpace(header)
if len(header) == 0 {
return
}
if header == defaultMatchAll {
c.allowHeaders = []string{defaultMatchAll}
return
}
c.allowHeaders = append(c.allowHeaders, header)
}
func (c *CorsConfig) AddAllowMethod(method string) {
method = strings.TrimSpace(method)
if len(method) == 0 {
return
}
if method == defaultMatchAll {
c.allowMethods = []string{defaultMatchAll}
return
}
c.allowMethods = append(c.allowMethods, strings.ToUpper(method))
}
func (c *CorsConfig) AddExposeHeader(header string) {
header = strings.TrimSpace(header)
if len(header) == 0 {
return
}
if header == defaultMatchAll {
c.exposeHeaders = []string{defaultMatchAll}
return
}
c.exposeHeaders = append(c.exposeHeaders, header)
}
func (c *CorsConfig) AddAllowOriginPattern(pattern string) {
pattern = strings.TrimSpace(pattern)
if len(pattern) == 0 {
return
}
originPattern := newOriginPatternFromString(pattern)
c.allowOriginPatterns = append(c.allowOriginPatterns, originPattern)
}
func (c *CorsConfig) SetAllowCredentials(allowCredentials bool) error {
if allowCredentials && len(c.allowOrigins) > 0 && c.allowOrigins[0] == defaultMatchAll {
return errors.New("can't set allowCredentials to true when allowOrigin is *")
}
c.allowCredentials = allowCredentials
return nil
}
func (c *CorsConfig) SetMaxAge(maxAge int) {
if maxAge <= 0 {
c.maxAge = defaultMaxAge
} else {
c.maxAge = maxAge
}
}
func (c *CorsConfig) Process(scheme string, host string, method string, headers [][2]string) (HttpCorsContext, error) {
scheme = strings.ToLower(strings.TrimSpace(scheme))
host = strings.ToLower(strings.TrimSpace(host))
method = strings.ToLower(strings.TrimSpace(method))
// Init httpCorsContext with default values
httpCorsContext := HttpCorsContext{IsValid: true, IsPreFlight: false, IsCorsRequest: false, AllowCredentials: false, MaxAge: 0}
// Get request origin, controlRequestMethod, controlRequestHeaders from http headers
origin := ""
controlRequestMethod := ""
controlRequestHeaders := ""
for _, header := range headers {
key := header[0]
// Get origin
if strings.ToLower(key) == strings.ToLower(HeaderOrigin) {
origin = strings.TrimSuffix(strings.TrimSpace(header[1]), "/")
}
// Get control request method & headers
if strings.ToLower(key) == strings.ToLower(HeaderControlRequestMethod) {
controlRequestMethod = strings.TrimSpace(header[1])
}
if strings.ToLower(key) == strings.ToLower(HeaderControlRequestHeaders) {
controlRequestHeaders = strings.TrimSpace(header[1])
}
}
// Parse if request is CORS and pre-flight request.
isCorsRequest := c.isCorsRequest(scheme, host, origin)
isPreFlight := c.isPreFlight(origin, method, controlRequestMethod)
httpCorsContext.IsCorsRequest = isCorsRequest
httpCorsContext.IsPreFlight = isPreFlight
// Skip when it is not CORS request
if !isCorsRequest {
httpCorsContext.IsValid = true
return httpCorsContext, nil
}
// Check origin
allowOrigin, originOk := c.checkOrigin(origin)
if !originOk {
// Reject: origin is not allowed
httpCorsContext.IsValid = false
httpCorsContext.ValidReason = fmt.Sprintf("origin:%s is not allowed", origin)
return httpCorsContext, nil
}
// Check method
requestMethod := method
if isPreFlight {
requestMethod = controlRequestMethod
}
allowMethods, methodOk := c.checkMethods(requestMethod)
if !methodOk {
// Reject: method is not allowed
httpCorsContext.IsValid = false
httpCorsContext.ValidReason = fmt.Sprintf("method:%s is not allowed", requestMethod)
return httpCorsContext, nil
}
// Check headers
allowHeaders, headerOK := c.checkHeaders(controlRequestHeaders)
if isPreFlight && !headerOK {
// Reject: headers are not allowed
httpCorsContext.IsValid = false
httpCorsContext.ValidReason = "Reject: headers are not allowed"
return httpCorsContext, nil
}
// Store result in httpCorsContext and return it.
httpCorsContext.AllowOrigin = allowOrigin
if isPreFlight {
httpCorsContext.AllowMethods = allowMethods
}
if isPreFlight && len(allowHeaders) > 0 {
httpCorsContext.AllowHeaders = allowHeaders
}
if isPreFlight && c.maxAge > 0 {
httpCorsContext.MaxAge = c.maxAge
}
if len(c.exposeHeaders) > 0 {
httpCorsContext.ExposeHeaders = strings.Join(c.exposeHeaders, ",")
}
httpCorsContext.AllowCredentials = c.allowCredentials
return httpCorsContext, nil
}
func (c *CorsConfig) checkOrigin(origin string) (string, bool) {
origin = strings.TrimSpace(origin)
if len(origin) == 0 {
return "", false
}
matchOrigin := strings.ToLower(origin)
// Check exact match
for _, allowOrigin := range c.allowOrigins {
if allowOrigin == defaultMatchAll {
return origin, true
}
if strings.ToLower(allowOrigin) == matchOrigin {
return origin, true
}
}
// Check pattern match
for _, allowOriginPattern := range c.allowOriginPatterns {
if allowOriginPattern.declaredPattern == defaultMatchAll || allowOriginPattern.pattern.MatchString(matchOrigin) {
return origin, true
}
}
return "", false
}
func (c *CorsConfig) checkHeaders(requestHeaders string) (string, bool) {
if len(c.allowHeaders) == 0 {
return "", false
}
if len(requestHeaders) == 0 {
return strings.Join(c.allowHeaders, ","), true
}
// Return all request headers when allowHeaders contains *
if c.allowHeaders[0] == defaultMatchAll {
return requestHeaders, true
}
checkHeaders := strings.Split(requestHeaders, ",")
// Each request header should be existed in allowHeaders configuration
for _, h := range checkHeaders {
isExist := false
for _, allowHeader := range c.allowHeaders {
if strings.ToLower(h) == strings.ToLower(allowHeader) {
isExist = true
break
}
}
if !isExist {
return "", false
}
}
return strings.Join(c.allowHeaders, ","), true
}
func (c *CorsConfig) checkMethods(requestMethod string) (string, bool) {
if len(requestMethod) == 0 {
return "", false
}
// Find method existed in allowMethods configuration
for _, method := range c.allowMethods {
if method == defaultMatchAll {
return defaultAllAllowMethods, true
}
if strings.ToLower(method) == strings.ToLower(requestMethod) {
return strings.Join(c.allowMethods, ","), true
}
}
return "", false
}
func (c *CorsConfig) isPreFlight(origin, method, controllerRequestMethod string) bool {
return len(origin) > 0 && strings.ToLower(method) == strings.ToLower(HttpMethodOptions) && len(controllerRequestMethod) > 0
}
func (c *CorsConfig) isCorsRequest(scheme, host, origin string) bool {
if len(origin) == 0 {
return false
}
url, err := url.Parse(strings.TrimSpace(origin))
if err != nil {
return false
}
// Check scheme
if strings.ToLower(scheme) != strings.ToLower(url.Scheme) {
return true
}
// Check host and port
port := ""
originPort := ""
originHost := ""
host, port = c.getHostAndPort(scheme, host)
originHost, originPort = c.getHostAndPort(url.Scheme, url.Host)
if host != originHost || port != originPort {
return true
}
return false
}
func (c *CorsConfig) getHostAndPort(scheme string, host string) (string, string) {
// Get host and port
scheme = strings.ToLower(scheme)
host = strings.ToLower(host)
port := ""
hosts := strings.Split(host, ":")
if len(hosts) > 1 {
host = hosts[0]
port = hosts[1]
}
// Get default port according scheme
if len(port) == 0 && scheme == protocolHttpName {
port = protocolHttpPort
}
if len(port) == 0 && scheme == protocolHttpsName {
port = protocolHttpsPort
}
return host, port
}

View File

@@ -0,0 +1,408 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestCorsConfig_getHostAndPort(t *testing.T) {
tests := []struct {
name string
scheme string
host string
wantHost string
wantPort string
}{
{
name: "http without port",
scheme: "http",
host: "http.example.com",
wantHost: "http.example.com",
wantPort: "80",
},
{
name: "https without port",
scheme: "https",
host: "http.example.com",
wantHost: "http.example.com",
wantPort: "443",
},
{
name: "http with port and case insensitive",
scheme: "hTTp",
host: "hTTp.Example.com:8080",
wantHost: "http.example.com",
wantPort: "8080",
},
{
name: "https with port and case insensitive",
scheme: "hTTps",
host: "hTTp.Example.com:8080",
wantHost: "http.example.com",
wantPort: "8080",
},
{
name: "protocal is not http",
scheme: "wss",
host: "hTTp.Example.com",
wantHost: "http.example.com",
wantPort: "",
},
{
name: "protocal is not http",
scheme: "wss",
host: "hTTp.Example.com:8080",
wantHost: "http.example.com",
wantPort: "8080",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CorsConfig{}
host, port := c.getHostAndPort(tt.scheme, tt.host)
assert.Equal(t, tt.wantHost, host)
assert.Equal(t, tt.wantPort, port)
})
}
}
func TestCorsConfig_isCorsRequest(t *testing.T) {
tests := []struct {
name string
scheme string
host string
origin string
want bool
}{
{
name: "blank origin",
scheme: "http",
host: "httpbin.example.com",
origin: "",
want: false,
},
{
name: "normal equal case with space and case ",
scheme: "http",
host: "httpbin.example.com",
origin: "http://hTTPbin.Example.com",
want: false,
},
{
name: "cors request with port diff",
scheme: "http",
host: "httpbin.example.com",
origin: " http://httpbin.example.com:8080 ",
want: true,
},
{
name: "cors request with scheme diff",
scheme: "http",
host: "httpbin.example.com",
origin: " https://HTTPpbin.Example.com ",
want: true,
},
{
name: "cors request with host diff",
scheme: "http",
host: "httpbin.example.com",
origin: " http://HTTPpbin.Example.org ",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CorsConfig{}
assert.Equalf(t, tt.want, c.isCorsRequest(tt.scheme, tt.host, tt.origin), "isCorsRequest(%v, %v, %v)", tt.scheme, tt.host, tt.origin)
})
}
}
func TestCorsConfig_isPreFlight(t *testing.T) {
tests := []struct {
name string
origin string
method string
controllerRequestMethod string
want bool
}{
{
name: "blank case",
origin: "",
method: "",
controllerRequestMethod: "",
want: false,
},
{
name: "normal case",
origin: "http://httpbin.example.com",
method: "Options",
controllerRequestMethod: "PUT",
want: true,
},
{
name: "bad case with diff method",
origin: "http://httpbin.example.com",
method: "GET",
controllerRequestMethod: "PUT",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CorsConfig{}
assert.Equalf(t, tt.want, c.isPreFlight(tt.origin, tt.method, tt.controllerRequestMethod), "isPreFlight(%v, %v, %v)", tt.origin, tt.method, tt.controllerRequestMethod)
})
}
}
func TestCorsConfig_checkMethods(t *testing.T) {
tests := []struct {
name string
allowMethods []string
requestMethod string
wantMethods string
wantOk bool
}{
{
name: "default *",
allowMethods: []string{"*"},
requestMethod: "GET",
wantMethods: defaultAllAllowMethods,
wantOk: true,
},
{
name: "normal allow case",
allowMethods: []string{"GET", "PUT", "HEAD"},
requestMethod: "get",
wantMethods: "GET,PUT,HEAD",
wantOk: true,
},
{
name: "forbidden case",
allowMethods: []string{"GET", "PUT", "HEAD"},
requestMethod: "POST",
wantMethods: "",
wantOk: false,
},
{
name: "blank method",
allowMethods: []string{"GET", "PUT", "HEAD"},
requestMethod: "",
wantMethods: "",
wantOk: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CorsConfig{
allowMethods: tt.allowMethods,
}
allowMethods, allowOk := c.checkMethods(tt.requestMethod)
assert.Equalf(t, tt.wantMethods, allowMethods, "checkMethods(%v)", tt.requestMethod)
assert.Equalf(t, tt.wantOk, allowOk, "checkMethods(%v)", tt.requestMethod)
})
}
}
func TestCorsConfig_checkHeaders(t *testing.T) {
tests := []struct {
name string
allowHeaders []string
requestHeaders string
wantHeaders string
wantOk bool
}{
{
name: "not pre-flight",
allowHeaders: []string{"Content-Type", "Authorization"},
requestHeaders: "",
wantHeaders: "Content-Type,Authorization",
wantOk: true,
},
{
name: "blank allowheaders case 1",
allowHeaders: []string{},
requestHeaders: "",
wantHeaders: "",
wantOk: false,
},
{
name: "blank allowheaders case 2",
requestHeaders: "Authorization",
wantHeaders: "",
wantOk: false,
},
{
name: "allowheaders *",
allowHeaders: []string{"*"},
requestHeaders: "Content-Type,Authorization",
wantHeaders: "Content-Type,Authorization",
wantOk: true,
},
{
name: "allowheader values 1",
allowHeaders: []string{"Content-Type", "Authorization"},
requestHeaders: "Content-Type,Authorization",
wantHeaders: "Content-Type,Authorization",
wantOk: true,
},
{
name: "allowheader values 2",
allowHeaders: []string{"Content-Type", "Authorization"},
requestHeaders: "",
wantHeaders: "Content-Type,Authorization",
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CorsConfig{
allowHeaders: tt.allowHeaders,
}
allowHeaders, allowOk := c.checkHeaders(tt.requestHeaders)
assert.Equalf(t, tt.wantHeaders, allowHeaders, "checkHeaders(%v)", tt.requestHeaders)
assert.Equalf(t, tt.wantOk, allowOk, "checkHeaders(%v)", tt.requestHeaders)
})
}
}
func TestCorsConfig_checkOrigin(t *testing.T) {
tests := []struct {
name string
allowOrigins []string
allowOriginPatterns []OriginPattern
origin string
wantOrigin string
wantOk bool
}{
{
name: "allowOrigins *",
allowOrigins: []string{defaultMatchAll},
allowOriginPatterns: []OriginPattern{},
origin: "http://Httpbin.Example.COM",
wantOrigin: "http://Httpbin.Example.COM",
wantOk: true,
},
{
name: "allowOrigins exact match case 1",
allowOrigins: []string{"http://httpbin.example.com"},
allowOriginPatterns: []OriginPattern{},
origin: "http://HTTPBin.EXample.COM",
wantOrigin: "http://HTTPBin.EXample.COM",
wantOk: true,
},
{
name: "allowOrigins exact match case 2",
allowOrigins: []string{"https://httpbin.example.com"},
allowOriginPatterns: []OriginPattern{},
origin: "http://HTTPBin.EXample.COM",
wantOrigin: "",
wantOk: false,
},
{
name: "OriginPattern pattern match with *",
allowOrigins: []string{},
allowOriginPatterns: []OriginPattern{
newOriginPatternFromString("*"),
},
origin: "http://HTTPBin.EXample.COM",
wantOrigin: "http://HTTPBin.EXample.COM",
wantOk: true,
},
{
name: "OriginPattern pattern match case with any port",
allowOrigins: []string{},
allowOriginPatterns: []OriginPattern{
newOriginPatternFromString("http://*.example.com:[*]"),
},
origin: "http://HTTPBin.EXample.COM",
wantOrigin: "http://HTTPBin.EXample.COM",
wantOk: true,
},
{
name: "OriginPattern pattern match case with any port",
allowOrigins: []string{},
allowOriginPatterns: []OriginPattern{
newOriginPatternFromString("http://*.example.com:[*]"),
},
origin: "http://HTTPBin.EXample.COM:10000",
wantOrigin: "http://HTTPBin.EXample.COM:10000",
wantOk: true,
},
{
name: "OriginPattern pattern match case with specail port 1",
allowOrigins: []string{},
allowOriginPatterns: []OriginPattern{
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
},
origin: "http://HTTPBin.EXample.COM:10000",
wantOrigin: "",
wantOk: false,
},
{
name: "OriginPattern pattern match case with specail port 2",
allowOrigins: []string{},
allowOriginPatterns: []OriginPattern{
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
},
origin: "http://HTTPBin.EXample.COM:9090",
wantOrigin: "http://HTTPBin.EXample.COM:9090",
wantOk: true,
},
{
name: "OriginPattern pattern match case with specail port 3",
allowOrigins: []string{},
allowOriginPatterns: []OriginPattern{
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
},
origin: "http://HTTPBin.EXample.org:9090",
wantOrigin: "",
wantOk: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CorsConfig{
allowOrigins: tt.allowOrigins,
allowOriginPatterns: tt.allowOriginPatterns,
}
allowOrigin, allowOk := c.checkOrigin(tt.origin)
assert.Equalf(t, tt.wantOrigin, allowOrigin, "checkOrigin(%v)", tt.origin)
assert.Equalf(t, tt.wantOk, allowOk, "checkOrigin(%v)", tt.origin)
})
}
}

View File

@@ -0,0 +1,67 @@
apiVersion: networking.higress.io/v1
kind: McpBridge
metadata:
name: mcp-cors-httpbin
namespace: higress-system
spec:
registries:
- domain: httpbin.org
name: httpbin
port: 80
type: dns
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/destination: httpbin.dns
higress.io/upstream-vhost: "httpbin.org"
higress.io/backend-protocol: HTTP
name: ingress-cors-httpbin
namespace: higress-system
spec:
ingressClassName: higress
rules:
- host: httpbin.example.com
http:
paths:
- backend:
resource:
apiGroup: networking.higress.io
kind: McpBridge
name: mcp-cors-httpbin
path: /
pathType: Prefix
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: wasm-cors-httpbin
namespace: higress-system
spec:
defaultConfigDisable: true
matchRules:
- config:
allow_origins:
- http://httpbin.example.net
allow_origin_patterns:
- http://*.example.com:[*]
- http://*.example.org:[9090,8080]
allow_methods:
- GET
- POST
- PATCH
allow_headers:
- Content-Type
- Token
- Authorization
expose_headers:
- X-Custom-Header
- X-Env-UTM
allow_credentials: true
max_age: 3600
configDisable: false
ingress:
- ingress-cors-httpbin
url: oci://docker.io/2456868764/cors:1.0.0
imagePullPolicy: Always

View File

@@ -0,0 +1,78 @@
static_resources:
listeners:
- name: main
address:
socket_address:
address: 0.0.0.0
port_value: 18000
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: auto
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains:
- "httpbin.example.com"
routes:
- match:
prefix: "/"
route:
cluster: httpbin
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
configuration:
"@type": type.googleapis.com/google.protobuf.StringValue
value: |-
{
"allow_origins": ["http://httpbin.example.net"],
"allow_origin_patterns": ["http://*.example.com:[*]", "http://*.example.org:[9090,8080]"],
"allow_methods": ["GET","PUT","POST", "PATCH", "HEAD", "OPTIONS"],
"allow_credentials": true,
"allow_headers":["Content-Type", "Token","Authorization"],
"expose_headers":["X-Custom-Header"],
"max_age": 3600
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
code:
local:
filename: "./main.wasm"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: httpbin
connect_timeout: 0.5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
dns_refresh_rate: 5s
dns_lookup_family: V4_ONLY
load_assignment:
cluster_name: httpbin
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: httpbin.org
port_value: 80
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 8001

View File

@@ -0,0 +1,21 @@
module cors
go 1.19
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230519024024-625c06e58f91
github.com/stretchr/testify v1.8.3
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
github.com/tidwall/gjson v1.14.4
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -0,0 +1,20 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,169 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"cors/config"
"fmt"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {
wrapper.SetCtx(
"cors",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
wrapper.ProcessResponseBodyBy(onHttpResponseBody),
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
)
}
func parseConfig(json gjson.Result, corsConfig *config.CorsConfig, log wrapper.Log) error {
log.Debug("parseConfig()")
allowOrigins := json.Get("allow_origins").Array()
for _, origin := range allowOrigins {
if err := corsConfig.AddAllowOrigin(origin.String()); err != nil {
log.Warnf("failed to AddAllowOrigin:%s, error:%v", origin, err)
}
}
allowOriginPatterns := json.Get("allow_origin_patterns").Array()
for _, pattern := range allowOriginPatterns {
corsConfig.AddAllowOriginPattern(pattern.String())
}
allowMethods := json.Get("allow_methods").Array()
for _, method := range allowMethods {
corsConfig.AddAllowMethod(method.String())
}
allowHeaders := json.Get("allow_headers").Array()
for _, header := range allowHeaders {
corsConfig.AddAllowHeader(header.String())
}
exposeHeaders := json.Get("expose_headers").Array()
for _, header := range exposeHeaders {
corsConfig.AddExposeHeader(header.String())
}
allowCredentials := json.Get("allow_credentials").Bool()
if err := corsConfig.SetAllowCredentials(allowCredentials); err != nil {
log.Warnf("failed to set AllowCredentials error: %v", err)
}
maxAge := json.Get("max_age").Int()
corsConfig.SetMaxAge(int(maxAge))
// Fill default values
corsConfig.FillDefaultValues()
log.Debugf("corsConfig:%+v", corsConfig)
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, corsConfig config.CorsConfig, log wrapper.Log) types.Action {
log.Debug("onHttpRequestHeaders()")
requestUrl, _ := proxywasm.GetHttpRequestHeader(":path")
method, _ := proxywasm.GetHttpRequestHeader(":method")
host := ctx.Host()
scheme := ctx.Scheme()
log.Debugf("scheme:%s, host:%s, method:%s, request:%s", scheme, host, method, requestUrl)
// Get headers
headers, _ := proxywasm.GetHttpRequestHeaders()
// Process request
httpCorsContext, err := corsConfig.Process(scheme, host, method, headers)
if err != nil {
log.Warnf("failed to process %s : %v", requestUrl, err)
return types.ActionContinue
}
log.Debugf("Process httpCorsContext:%+v", httpCorsContext)
// Set HttpContext
ctx.SetContext(config.HttpContextKey, httpCorsContext)
// Response forbidden when it is not valid cors request
if !httpCorsContext.IsValid {
headers := make([][2]string, 0)
headers = append(headers, [2]string{config.HeaderPluginTrace, "trace"})
proxywasm.SendHttpResponse(403, headers, []byte("Invalid CORS request"), -1)
return types.ActionPause
}
// Response directly when it is cors preflight request
if httpCorsContext.IsPreFlight {
headers := make([][2]string, 0)
headers = append(headers, [2]string{config.HeaderPluginTrace, "trace"})
proxywasm.SendHttpResponse(200, headers, nil, -1)
return types.ActionPause
}
return types.ActionContinue
}
func onHttpRequestBody(ctx wrapper.HttpContext, corsConfig config.CorsConfig, body []byte, log wrapper.Log) types.Action {
log.Debug("onHttpRequestBody()")
return types.ActionContinue
}
func onHttpResponseHeaders(ctx wrapper.HttpContext, corsConfig config.CorsConfig, log wrapper.Log) types.Action {
log.Debug("onHttpResponseHeaders()")
// Remove trace header if existed
proxywasm.RemoveHttpResponseHeader(config.HeaderPluginTrace)
// Remove upstream cors response headers if existed
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowOrigin)
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowMethods)
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlExposeHeaders)
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowCredentials)
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlMaxAge)
// Add debug header
proxywasm.AddHttpResponseHeader(config.HeaderPluginDebug, corsConfig.GetVersion())
// Restore httpCorsContext from HttpContext
httpCorsContext, ok := ctx.GetContext(config.HttpContextKey).(config.HttpCorsContext)
if !ok {
log.Debug("restore httpCorsContext from HttpContext error")
return types.ActionContinue
}
log.Debugf("Restore httpCorsContext:%+v", httpCorsContext)
// Skip which it is not valid or not cors request
if !httpCorsContext.IsValid || !httpCorsContext.IsCorsRequest {
return types.ActionContinue
}
// Add Cors headers when it is cors and valid request
if len(httpCorsContext.AllowOrigin) > 0 {
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowOrigin, httpCorsContext.AllowOrigin)
}
if len(httpCorsContext.AllowMethods) > 0 {
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowMethods, httpCorsContext.AllowMethods)
}
if len(httpCorsContext.AllowHeaders) > 0 {
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowHeaders, httpCorsContext.AllowHeaders)
}
if len(httpCorsContext.ExposeHeaders) > 0 {
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlExposeHeaders, httpCorsContext.ExposeHeaders)
}
if httpCorsContext.AllowCredentials {
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowCredentials, "true")
}
if httpCorsContext.MaxAge > 0 {
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlMaxAge, fmt.Sprintf("%d", httpCorsContext.MaxAge))
}
return types.ActionContinue
}
func onHttpResponseBody(ctx wrapper.HttpContext, corsConfig config.CorsConfig, body []byte, log wrapper.Log) types.Action {
log.Debug("onHttpResponseBody()")
return types.ActionContinue
}

View File

@@ -0,0 +1,3 @@
FROM scratch
COPY local/main.wasm /plugin.wasm

View File

@@ -0,0 +1,52 @@
# 功能说明
waf插件实现了基于ModSecurity的规则防护引擎可以根据用户配置的规则屏蔽可疑请求并支持OWASP CRS为站点提供基础的防护功能。
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
| -------- | -------- | -------- | -------- | -------- |
| useCRS | bool | 选填 | false | 是否开启OWASP CRS详情可参考[coreruleset](https://github.com/coreruleset/coreruleset/tree/v3.3.2) |
| secRules | array of string | 选填 | - | 用户自定义的waf防护规则语法规则可参考[ModSecurity中文手册](http://www.modsecurity.cn/chm/) |
# 配置示例
```yaml
useCRS: true
secRules:
- "SecDebugLogLevel 3"
- "SecRuleEngine On"
- "SecAction \"id:100,phase:1,pass\""
- "SecRule REQUEST_URI \"@streq /admin\" \"id:101,phase:1,t:lowercase,deny\""
- "SecRule REQUEST_BODY \"@rx maliciouspayload\" \"id:102,phase:2,t:lowercase,deny\""
```
根据该配置,以下请求将被禁止访问:
```bash
curl http://example.com/admin
curl http://example.com -d "maliciouspayload"
```
# 对特定路由或域名开启
```yaml
useCRS: true
secRules:
- "SecDebugLogLevel 3"
- "SecRuleEngine On"
- "SecAction \"id:100,phase:1,pass\""
- "SecRule REQUEST_URI \"@streq /admin\" \"id:101,phase:1,t:lowercase,deny\""
- "SecRule REQUEST_BODY \"@rx maliciouspayload\" \"id:102,phase:2,t:lowercase,deny\""
_rules_:
- _match_route_:
- "route-1"
secRules:
- "SecDebugLogLevel 3"
- "SecRuleEngine On"
- "SecAction \"id:102,phase:1,deny\""
- _match_domain_:
- "*.example.com"
- test.com
secRules:
- "SecDebugLogLevel 3"
- "SecRuleEngine On"
- "SecAction \"id:102,phase:1,pass\""
```
此例 `_match_route_` 中指定的 `route-1` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置; 此例 `_match_domain_` 中指定的 `*.example.com``test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置; 配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。

View File

@@ -0,0 +1,37 @@
module github.com/corazawaf/coraza-proxy-wasm
go 1.19
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230504075705-7e358eb1db7c
github.com/corazawaf/coraza-wasilibs v0.0.0-20230408002644-e2e3af21f503
github.com/corazawaf/coraza/v3 v3.0.0-rc.1.0.20230407165813-a18681b1ec28
github.com/stretchr/testify v1.8.0
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
github.com/tidwall/gjson v1.14.4
github.com/wasilibs/nottinygc v0.2.0
)
require (
github.com/corazawaf/libinjection-go v0.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tetratelabs/wazero v1.0.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/wasilibs/go-aho-corasick v0.3.0 // indirect
github.com/wasilibs/go-libinjection v0.2.1 // indirect
github.com/wasilibs/go-re2 v1.0.0 // indirect
golang.org/x/net v0.9.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/binaryregexp v0.2.0 // indirect
)
replace (
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230504075705-7e358eb1db7c => github.com/rinfx/higress/plugins/wasm-go v0.0.0-20230508112120-f2d89b0606ee
)

View File

@@ -0,0 +1,65 @@
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230504075705-7e358eb1db7c h1:RKZJGYAkczZVqvJ/CuNJSY6YI5oyJjzm12MVzf3Z7/U=
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230504075705-7e358eb1db7c/go.mod h1:AzSnkuon5c26nIePTiJQIAFsKdhkNdncLcTuahpGtQs=
github.com/corazawaf/coraza-wasilibs v0.0.0-20230408002644-e2e3af21f503 h1:hGXspDwUBHQUne1NT2D6PmkR9wFCXsibjaJpz7xhf+g=
github.com/corazawaf/coraza-wasilibs v0.0.0-20230408002644-e2e3af21f503/go.mod h1:bTc+NV7T2wQevFQHDDWhD/+IAA5bvKbbK4CxzfvJx/o=
github.com/corazawaf/coraza/v3 v3.0.0-rc.1.0.20230407165813-a18681b1ec28 h1:Jrlvhe4YCR/PMCazDEBeun/XTYhlzczBN0WN4/ejORo=
github.com/corazawaf/coraza/v3 v3.0.0-rc.1.0.20230407165813-a18681b1ec28/go.mod h1:TKREBLh55w3SiBbLsQpH9EFzjBAmEUH4KRaZ/kFYz20=
github.com/corazawaf/libinjection-go v0.1.2 h1:oeiV9pc5rvJ+2oqOqXEAMJousPpGiup6f7Y3nZj5GoM=
github.com/corazawaf/libinjection-go v0.1.2/go.mod h1:OP4TM7xdJ2skyXqNX1AN1wN5nNZEmJNuWbNPOItn7aw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9 h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9/go.mod h1:EHPiTAKtiFmrMldLUNswFwfZ2eJIYBHktdaUTZxYWRw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rinfx/higress/plugins/wasm-go v0.0.0-20230508112120-f2d89b0606ee h1:5qHbEcei04hDcUTzvoFNtYtZiewnTgVv6wR9co8Ih6c=
github.com/rinfx/higress/plugins/wasm-go v0.0.0-20230508112120-f2d89b0606ee/go.mod h1:AzSnkuon5c26nIePTiJQIAFsKdhkNdncLcTuahpGtQs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
github.com/tetratelabs/wazero v1.0.1 h1:xyWBoGyMjYekG3mEQ/W7xm9E05S89kJ/at696d/9yuc=
github.com/tetratelabs/wazero v1.0.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/wasilibs/go-aho-corasick v0.3.0 h1:ScfPQhAwop/ELIkwY0dfMTFb/bwOdYI/MB3mkX2WOZI=
github.com/wasilibs/go-aho-corasick v0.3.0/go.mod h1:LKW6EW9NWuWYE8PII+sFpRbbY3UcrMUgfUTkGaoWyMY=
github.com/wasilibs/go-libinjection v0.2.1 h1:1aSwyE4oNpPGpFw3i3hoM15sF3qn1s4P0jC2jgFM2Qk=
github.com/wasilibs/go-libinjection v0.2.1/go.mod h1:ZUoVe+HLQYq+QPBNTSgg3fxGvZsvXiDbi0UomBlsGzo=
github.com/wasilibs/go-re2 v1.0.0 h1:pvrqtMzZgTMHVPfXJrk4YZwiqIXOKdfo5aed6CzUAW4=
github.com/wasilibs/go-re2 v1.0.0/go.mod h1:8g69JapfgjSCx49dKOQij1dqA3sOvoH5NteaUy1X0SA=
github.com/wasilibs/nottinygc v0.2.0 h1:cXz2Ac9bVMLkpuOlUlPQMWowjw0K2cOErXZOFdAj7yE=
github.com/wasilibs/nottinygc v0.2.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View File

@@ -0,0 +1,14 @@
//go:build tinygo
package main
import _ "github.com/wasilibs/nottinygc"
// Compiled by nottinygc for delayed free but Envoy doesn't stub it yet,
// luckily nottinygc doesn't actually call the function, so it's fine to
// stub it out.
//export sched_yield
func sched_yield() int32 {
return 0
}

View File

@@ -0,0 +1,3 @@
FROM liuxr25/flask-helloworld:latest
COPY app.py /work/app.py

View File

@@ -0,0 +1,14 @@
from flask import Flask, request
app = Flask(__name__)
@app.route("/flask/test1", methods=["GET", "POST"])
def test1():
return "body normal", 200, [("test-header", "hahaha")]
@app.route("/flask/test2", methods=["GET", "POST"])
def test2():
return "body attack", 200, []
if __name__ == "__main__":
app.run("0.0.0.0", 5000)

View File

@@ -0,0 +1,96 @@
services:
httpbin:
image: kennethreitz/httpbin
environment:
- MAX_BODY_SIZE=15728640 # 15 MiB
ports:
- 8083:8080
command:
- "gunicorn"
- "-b"
- "0.0.0.0:8080"
- "httpbin:app"
- "-k"
- "gevent"
- --log-file
- /home/envoy/logs/httpbin.log
volumes:
- logs:/home/envoy/logs:rw
flask:
# image: liuxr25/flask-helloworld:latest
build: .
environment:
- MAX_BODY_SIZE=15728640 # 15 MiB
ports:
- 8084:5000
chown:
image: alpine:3.16
command:
- /bin/sh
- -c
- chown -R 101:101 /home/envoy/logs
volumes:
- logs:/home/envoy/logs:rw
envoy:
depends_on:
- chown
- httpbin
- flask
image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/envoy:1.20
command:
- -c
- /conf/envoy-config.yaml
- --log-level
- info
- --component-log-level
- wasm:debug
- --log-format [%Y-%m-%d %T.%f][%t][%l][%n] [%g:%#] %v
- --log-path
- /home/envoy/logs/envoy.log
volumes:
- .:/build
- .:/conf
- logs:/home/envoy/logs:rw
ports:
- 8080:8080
- 8082:8082
# envoy-logs:
# depends_on:
# - envoy
# - wasm-logs
# image: debian:11-slim
# entrypoint: bash
# command:
# - -c
# - tail -c +0 -f /home/envoy/logs/envoy.log
# volumes:
# - logs:/home/envoy/logs:ro
wasm-logs:
depends_on:
- envoy
image: debian:11-slim
entrypoint: bash
command:
- -c
- tail -c +0 -f /home/envoy/logs/envoy.log | grep --line-buffered "[critical][wasm]"
volumes:
- logs:/home/envoy/logs:ro
# debug-logs:
# depends_on:
# - envoy
# image: debian:11-slim
# entrypoint: bash
# command:
# - -c
# - tail -c +0 -f /home/envoy/logs/envoy.log | grep --line-buffered "unreachable"
# volumes:
# - logs:/home/envoy/logs:ro
volumes:
logs:

View File

@@ -0,0 +1,143 @@
stats_config:
stats_tags:
# Envoy extracts the first matching group as a value.
# See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/metrics/v3/stats.proto#config-metrics-v3-statsconfig.
- tag_name: phase
regex: "(_phase=([a-z_]+))"
- tag_name: rule_id
regex: "(_ruleid=([0-9]+))"
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- 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: ingress_http
codec_type: auto
route_config:
virtual_hosts:
- name: local_route
domains:
- "*"
routes:
- name: "route_1"
match:
path: "/headers"
route:
cluster: httpbin_server
- name: "route_2"
match:
path: "/user-agent"
route:
cluster: httpbin_server
- name: "route_flask"
match:
prefix: "/flask"
route:
cluster: flask_server
- name: "route_httpbin"
match:
prefix: "/"
route:
cluster: httpbin_server
# - name: "route_mock"
# match:
# prefix: "/"
# direct_response:
# status: 200
# body:
# inline_string: "mock response\n"
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
config:
name: "coraza-filter"
root_id: ""
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"useCRS": true,
"secRules": [
"SecDebugLogLevel 3",
"SecRuleEngine On",
"SecRule REQUEST_URI \"@streq /admin\" \"id:101,phase:1,t:lowercase,deny\"",
"SecRule REQUEST_BODY \"@rx maliciouspayload\" \"id:102,phase:2,t:lowercase,deny\"",
"SecRule RESPONSE_HEADERS::status \"@rx 406\" \"id:103,phase:3,t:lowercase,deny\"",
"SecRule RESPONSE_HEADERS:test-header \"@streq hahaha\" \"id:104,phase:3,t:lowercase,deny\"",
"SecRule RESPONSE_BODY \"@rx attack\" \"id:105,phase:4,t:lowercase,deny\""
],
"_rules_": [
{
"_match_route_": [
"route_1"
],
"secRules": [
"SecDebugLogLevel 3",
"SecRuleEngine On",
"SecAction \"id:102,phase:1,deny\""
]
},
{
"_match_route_": [
"route_2"
],
"secRules": [
"SecDebugLogLevel 3",
"SecRuleEngine On",
"SecAction \"id:102,phase:1,pass\""
]
}
]
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
vm_id: "10086"
code:
local:
filename: "build/main.wasm"
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: httpbin_server
connect_timeout: 6000s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: httpbin_server
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: httpbin
port_value: 8080
- name: flask_server
connect_timeout: 6000s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: flask_server
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: flask
port_value: 5000
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 8082

View File

@@ -0,0 +1,16 @@
//go:build ignore
// +build ignore
// Entrypoint to mage for running without needing to install the command.
// https://magefile.org/zeroinstall/
package main
import (
"os"
"github.com/magefile/mage/mage"
)
func main() {
os.Exit(mage.Main())
}

View File

@@ -0,0 +1,16 @@
module github.com/corazawaf/coraza-proxy-wasm/magefiles
go 1.19
require (
fortio.org/fortio v1.38.4
github.com/magefile/mage v1.14.0
github.com/tetratelabs/wabin v0.0.0-20220927005300-3b0fbf39a46a
)
require (
github.com/google/uuid v1.3.0 // indirect
golang.org/x/exp v0.0.0-20221111204811-129d8d6c17ab // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/text v0.4.0 // indirect
)

View File

@@ -0,0 +1,21 @@
fortio.org/assert v1.1.2 h1:t6WGDqPD5VFrUvx30U0+3mgXXcoPonrdKqt0vfJHn8E=
fortio.org/fortio v1.38.4 h1:HkVsu9E4emMU+i2D2HjPll1Dbu5IqCRTeKeoiYWxFWA=
fortio.org/fortio v1.38.4/go.mod h1:xZTReCI6wlPJQN+JssVO6jsqXJV2vC32dcZJrUaqmcU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tetratelabs/wabin v0.0.0-20220927005300-3b0fbf39a46a h1:P0R3+CTAT7daT8ig5gh9GEd/eDQ5md1xl4pkYMcwOqg=
github.com/tetratelabs/wabin v0.0.0-20220927005300-3b0fbf39a46a/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk=
golang.org/x/exp v0.0.0-20221111204811-129d8d6c17ab h1:1S7USr8/C0Sgk4egxq4zZ07zYt2Xh1IiFp8hUMXH/us=
golang.org/x/exp v0.0.0-20221111204811-129d8d6c17ab/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -0,0 +1,81 @@
package main
import (
"fmt"
"net/http"
"time"
"fortio.org/fortio/fhttp"
"fortio.org/fortio/fnet"
"fortio.org/fortio/periodic"
"github.com/magefile/mage/sh"
)
// LoadTest runs load tests against the ftw deployment.
func LoadTest() error {
for _, threads := range []int{1, 2, 4} {
for _, payloadSize := range []int{0, 100, 1000, 10000} {
for _, conf := range []string{"envoy-config.yaml", "envoy-config-nowasm.yaml"} {
if err := doLoadTest(conf, payloadSize, threads); err != nil {
return err
}
}
}
}
return nil
}
func doLoadTest(conf string, payloadSize int, threads int) error {
if err := sh.RunV("docker-compose", "--file", "ftw/docker-compose.yml", "build", "--pull"); err != nil {
return err
}
defer func() {
_ = sh.RunV("docker-compose", "--file", "ftw/docker-compose.yml", "kill")
_ = sh.RunV("docker-compose", "--file", "ftw/docker-compose.yml", "down", "-v")
}()
if err := sh.RunWithV(map[string]string{"ENVOY_CONFIG": fmt.Sprintf("/conf/%s", conf)}, "docker-compose",
"--file", "ftw/docker-compose.yml", "run", "--service-ports", "--rm", "-d", "envoy"); err != nil {
return err
}
// Wait for Envoy to start.
for i := 0; i < 1000; i++ {
if resp, err := http.Get("http://localhost:8080/anything"); err != nil {
continue
} else {
if resp.Body != nil {
resp.Body.Close()
}
if resp.StatusCode == http.StatusOK {
break
}
}
time.Sleep(50 * time.Millisecond)
}
opts := &fhttp.HTTPRunnerOptions{
RunnerOptions: periodic.RunnerOptions{
QPS: 100,
NumThreads: threads,
Duration: 10 * time.Second,
},
HTTPOptions: fhttp.HTTPOptions{
URL: "http://localhost:8080/anything",
Payload: fnet.GenerateRandomPayload(payloadSize),
},
}
fmt.Printf("Running load test with config=%s, payloadSize=%d, threads=%d\n", conf, payloadSize, threads)
res, err := fhttp.RunHTTPTest(opts)
if err != nil {
return err
}
rr := res.Result()
fmt.Printf("All done %d calls (plus %d warmup) %.3f ms avg, %.1f qps\n",
rr.DurationHistogram.Count,
0,
1000.*rr.DurationHistogram.Avg,
rr.ActualQPS)
return nil
}

View File

@@ -0,0 +1,271 @@
package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
"github.com/tetratelabs/wabin/binary"
"github.com/tetratelabs/wabin/wasm"
)
var minGoVersion = "1.19"
var tinygoMinorVersion = "0.27"
var addLicenseVersion = "04bfe4ee9ca5764577b029acc6a1957fd1997153" // https://github.com/google/addlicense
var golangCILintVer = "v1.48.0" // https://github.com/golangci/golangci-lint/releases
var gosImportsVer = "v0.3.1" // https://github.com/rinchsan/gosimports/releases/tag/v0.3.1
var errCommitFormatting = errors.New("files not formatted, please commit formatting changes")
var errNoGitDir = errors.New("no .git directory found")
func init() {
for _, check := range []func() error{
checkTinygoVersion,
checkGoVersion,
} {
if err := check(); err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
}
}
// checkGoVersion checks the minimum version of Go is supported.
func checkGoVersion() error {
v, err := sh.Output("go", "version")
if err != nil {
return fmt.Errorf("unexpected go error: %v", err)
}
// Version can/cannot include patch version e.g.
// - go version go1.19 darwin/arm64
// - go version go1.19.2 darwin/amd64
versionRegex := regexp.MustCompile("go([0-9]+).([0-9]+).?([0-9]+)?")
compare := versionRegex.FindStringSubmatch(v)
if len(compare) != 4 {
return fmt.Errorf("unexpected go semver: %q", v)
}
compare = compare[1:]
if compare[2] == "" {
compare[2] = "0"
}
base := strings.SplitN(minGoVersion, ".", 3)
if len(base) == 2 {
base = append(base, "0")
}
for i := 0; i < 3; i++ {
baseN, _ := strconv.Atoi(base[i])
compareN, _ := strconv.Atoi(compare[i])
if baseN > compareN {
return fmt.Errorf("unexpected go version, minimum want %q, have %q", minGoVersion, strings.Join(compare, "."))
}
}
return nil
}
// checkTinygoVersion checks that exactly the right tinygo version is supported because
// tinygo isn't stable yet.
func checkTinygoVersion() error {
v, err := sh.Output("tinygo", "version")
if err != nil {
return fmt.Errorf("unexpected tinygo error: %v", err)
}
// Assume a dev build is valid.
if strings.Contains(v, "-dev") {
return nil
}
if !strings.HasPrefix(v, fmt.Sprintf("tinygo version %s", tinygoMinorVersion)) {
return fmt.Errorf("unexpected tinygo version, wanted %s", tinygoMinorVersion)
}
return nil
}
// Format formats code in this repository.
func Format() error {
if err := sh.RunV("go", "mod", "tidy"); err != nil {
return err
}
// addlicense strangely logs skipped files to stderr despite not being erroneous, so use the long sh.Exec form to
// discard stderr too.
if _, err := sh.Exec(map[string]string{}, io.Discard, io.Discard, "go", "run", fmt.Sprintf("github.com/google/addlicense@%s", addLicenseVersion),
"-c", "The OWASP Coraza contributors",
"-s=only",
"-y=",
"-ignore", "**/*.yml",
"-ignore", "**/*.yaml",
"-ignore", "examples/**", "."); err != nil {
return err
}
return sh.RunV("go", "run", fmt.Sprintf("github.com/rinchsan/gosimports/cmd/gosimports@%s", gosImportsVer),
"-w",
"-local",
"github.com/corazawaf/coraza-proxy-wasm",
".")
}
// Lint verifies code quality.
func Lint() error {
if err := sh.RunV("go", "run", fmt.Sprintf("github.com/golangci/golangci-lint/cmd/golangci-lint@%s", golangCILintVer), "run"); err != nil {
return err
}
mg.SerialDeps(Format)
if sh.Run("git", "diff", "--exit-code") != nil {
return errCommitFormatting
}
return nil
}
// Test runs all unit tests.
func Test() error {
return sh.RunV("go", "test", "./...")
}
// Coverage runs tests with coverage and race detector enabled.
func Coverage() error {
if err := os.MkdirAll("build", 0755); err != nil {
return err
}
if err := sh.RunV("go", "test", "-race", "-coverprofile=build/coverage.txt", "-covermode=atomic", "-coverpkg=./...", "./..."); err != nil {
return err
}
return sh.RunV("go", "tool", "cover", "-html=build/coverage.txt", "-o", "build/coverage.html")
}
// Doc runs godoc, access at http://localhost:6060
func Doc() error {
return sh.RunV("go", "run", "golang.org/x/tools/cmd/godoc@latest", "-http=:6060")
}
// Check runs lint and tests.
func Check() {
mg.SerialDeps(Lint, Test)
}
// Build builds the Coraza wasm plugin.
func Build() error {
if err := os.MkdirAll("local", 0755); err != nil {
return err
}
buildTags := []string{"custommalloc", "no_fs_access"}
if os.Getenv("TIMING") == "true" {
buildTags = append(buildTags, "timing", "proxywasm_timing")
}
if os.Getenv("MEMSTATS") == "true" {
buildTags = append(buildTags, "memstats")
}
buildTagArg := fmt.Sprintf("-tags='%s'", strings.Join(buildTags, " "))
// ~100MB initial heap
initialPages := 2100
if ipEnv := os.Getenv("INITIAL_PAGES"); ipEnv != "" {
if ip, err := strconv.Atoi(ipEnv); err != nil {
return err
} else {
initialPages = ip
}
}
if err := sh.RunV("tinygo", "build", "-gc=custom", "-opt=2", "-o", filepath.Join("local", "mainraw.wasm"), "-scheduler=none", "-target=wasi", buildTagArg); err != nil {
return err
}
if err := patchWasm(filepath.Join("local", "mainraw.wasm"), filepath.Join("local", "main.wasm"), initialPages); err != nil {
return err
}
if err := sh.RunV("rm", filepath.Join("local", "mainraw.wasm")); err != nil {
return err
}
return nil
}
// E2e runs e2e tests with a built plugin against the example deployment. Requires docker-compose.
func E2e() error {
if err := sh.RunV("docker-compose", "--file", "e2e/docker-compose.yml", "build", "--pull"); err != nil {
return err
}
return sh.RunV("docker-compose", "-f", "e2e/docker-compose.yml", "up", "--abort-on-container-exit", "tests")
}
// Ftw runs ftw tests with a built plugin and Envoy. Requires docker-compose.
func Ftw() error {
if err := sh.RunV("docker-compose", "--file", "ftw/docker-compose.yml", "build", "--pull"); err != nil {
return err
}
defer func() {
_ = sh.RunV("docker-compose", "--file", "ftw/docker-compose.yml", "down", "-v")
}()
env := map[string]string{
"FTW_CLOUDMODE": os.Getenv("FTW_CLOUDMODE"),
"FTW_INCLUDE": os.Getenv("FTW_INCLUDE"),
"ENVOY_IMAGE": os.Getenv("ENVOY_IMAGE"),
}
if os.Getenv("ENVOY_NOWASM") == "true" {
env["ENVOY_CONFIG"] = "/conf/envoy-config-nowasm.yaml"
}
task := "ftw"
if os.Getenv("MEMSTATS") == "true" {
task = "ftw-memstats"
}
return sh.RunWithV(env, "docker-compose", "--file", "ftw/docker-compose.yml", "run", "--rm", task)
}
// RunExample spins up the test environment, access at http://localhost:8080. Requires docker-compose.
func RunExample() error {
return sh.RunWithV(map[string]string{"ENVOY_IMAGE": os.Getenv("ENVOY_IMAGE")}, "docker-compose", "--file", "example/docker-compose.yml", "up", "-d", "envoy-logs")
}
// TeardownExample tears down the test environment. Requires docker-compose.
func TeardownExample() error {
return sh.RunV("docker-compose", "--file", "example/docker-compose.yml", "down")
}
var Default = Build
func patchWasm(inPath, outPath string, initialPages int) error {
raw, err := os.ReadFile(inPath)
if err != nil {
return err
}
mod, err := binary.DecodeModule(raw, wasm.CoreFeaturesV2)
if err != nil {
return err
}
mod.MemorySection.Min = uint32(initialPages)
for _, imp := range mod.ImportSection {
switch {
case imp.Name == "fd_filestat_get":
imp.Name = "fd_fdstat_get"
case imp.Name == "path_filestat_get":
imp.Module = "env"
imp.Name = "proxy_get_header_map_value"
}
}
out := binary.EncodeModule(mod)
if err = os.WriteFile(outPath, out, 0644); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,14 @@
package main
import (
"github.com/corazawaf/coraza-proxy-wasm/wasmplugin"
wasilibs "github.com/corazawaf/coraza-wasilibs"
)
func main() {
wasilibs.RegisterRX()
wasilibs.RegisterPM()
wasilibs.RegisterSQLi()
wasilibs.RegisterXSS()
wasmplugin.PluginStart()
}

View File

@@ -0,0 +1,79 @@
package wasmplugin
import (
"embed"
"fmt"
"io/fs"
"strings"
)
var (
//go:embed rules
crs embed.FS
root fs.FS
)
func init() {
rules, _ := fs.Sub(crs, "rules")
root = &rulesFS{
rules,
map[string]string{
"@recommended-conf": "coraza.conf-recommended.conf",
"@demo-conf": "coraza-demo.conf",
"@crs-setup-demo-conf": "crs-setup-demo.conf",
"@ftw-conf": "ftw-config.conf",
"@crs-setup-conf": "crs-setup.conf.example",
},
map[string]string{
"@owasp_crs": "crs",
},
}
}
type rulesFS struct {
fs fs.FS
filesMapping map[string]string
dirsMapping map[string]string
}
func (r rulesFS) Open(name string) (fs.File, error) {
return r.fs.Open(r.mapPath(name))
}
func (r rulesFS) ReadDir(name string) ([]fs.DirEntry, error) {
for a, dst := range r.dirsMapping {
if a == name {
return fs.ReadDir(r.fs, dst)
}
prefix := a + "/"
if strings.HasPrefix(name, prefix) {
return fs.ReadDir(r.fs, fmt.Sprintf("%s/%s", dst, name[len(prefix):]))
}
}
return fs.ReadDir(r.fs, name)
}
func (r rulesFS) ReadFile(name string) ([]byte, error) {
return fs.ReadFile(r.fs, r.mapPath(name))
}
func (r rulesFS) mapPath(p string) string {
if strings.IndexByte(p, '/') != -1 {
// is not in root, hence we can do dir mapping
for a, dst := range r.dirsMapping {
prefix := a + "/"
if strings.HasPrefix(p, prefix) {
return fmt.Sprintf("%s/%s", dst, p[len(prefix):])
}
}
}
for a, dst := range r.filesMapping {
if a == p {
return dst
}
}
return p
}

View File

@@ -0,0 +1,339 @@
package wasmplugin
import (
"errors"
"strconv"
"strings"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/corazawaf/coraza/v3"
ctypes "github.com/corazawaf/coraza/v3/types"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func PluginStart() {
wrapper.SetCtx(
"waf-plugin-go",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
wrapper.ProcessResponseBodyBy(onHttpResponseBody),
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
wrapper.ProcessStreamDoneBy(onHttpStreamDone),
)
}
type WafConfig struct {
waf coraza.WAF
//tx ctypes.Transaction
}
func parseConfig(json gjson.Result, config *WafConfig, log wrapper.Log) error {
var secRules []string
var value gjson.Result
value = json.Get("useCRS")
if value.Exists() {
if value.Bool() {
secRules = append(secRules, "Include @demo-conf")
secRules = append(secRules, "Include @crs-setup-demo-conf")
secRules = append(secRules, "Include @owasp_crs/*.conf")
secRules = append(secRules, "SecRuleEngine On")
}
}
value = json.Get("secRules")
if value.Exists() {
for _, item := range json.Get("secRules").Array() {
rule := item.String()
secRules = append(secRules, rule)
}
}
// log.Debugf("[rinfx log] %s", strings.Join(secRules, "\n"))
conf := coraza.NewWAFConfig().WithRootFS(root)
// error: Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_filestat_get
// because without fs.go
waf, err := coraza.NewWAF(conf.WithDirectives(strings.Join(secRules, "\n")))
config.waf = waf
if err != nil {
log.Errorf("Failed to create waf conf: %v", err)
return errors.New("failed to create waf conf")
}
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config WafConfig, log wrapper.Log) types.Action {
ctx.SetContext("interruptionHandled", false)
ctx.SetContext("processedRequestBody", false)
ctx.SetContext("processedResponseBody", false)
ctx.SetContext("tx", config.waf.NewTransaction())
tx := ctx.GetContext("tx").(ctypes.Transaction)
// Note the pseudo-header :path includes the query.
// See https://httpwg.org/specs/rfc9113.html#rfc.section.8.3.1
uri, err := proxywasm.GetHttpRequestHeader(":path")
if err != nil {
log.Error("Failed to get :path")
return types.ActionContinue
}
// This currently relies on Envoy's behavior of mapping all requests to HTTP/2 semantics
// and its request properties, but they may not be true of other proxies implementing
// proxy-wasm.
if tx.IsRuleEngineOff() {
// log.Infof("[rinfx log] OnHttpRequestHeaders, RuleEngine Off, url = %s", uri)
return types.ActionContinue
}
// OnHttpRequestHeaders does not terminate if IP/Port retrieve goes wrong
srcIP, srcPort := retrieveAddressInfo(log, "source")
dstIP, dstPort := retrieveAddressInfo(log, "destination")
tx.ProcessConnection(srcIP, srcPort, dstIP, dstPort)
// proxywasm.LogInfof("[rinfx log] OnHttpRequestHeaders, RuleEngine On, url = %s", uri)
method, err := proxywasm.GetHttpRequestHeader(":method")
if err != nil {
log.Error("Failed to get :method")
return types.ActionContinue
}
protocol, err := proxywasm.GetProperty([]string{"request", "protocol"})
if err != nil {
// TODO(anuraaga): HTTP protocol is commonly required in WAF rules, we should probably
// fail fast here, but proxytest does not support properties yet.
protocol = []byte("HTTP/2.0")
}
ctx.SetContext("httpProtocol", string(protocol))
tx.ProcessURI(uri, method, string(protocol))
hs, err := proxywasm.GetHttpRequestHeaders()
if err != nil {
log.Error("Failed to get request headers")
return types.ActionContinue
}
for _, h := range hs {
tx.AddRequestHeader(h[0], h[1])
}
// CRS rules tend to expect Host even with HTTP/2
authority, err := proxywasm.GetHttpRequestHeader(":authority")
if err == nil {
tx.AddRequestHeader("Host", authority)
tx.SetServerName(parseServerName(log, authority))
}
interruption := tx.ProcessRequestHeaders()
if interruption != nil {
return handleInterruption(ctx, "http_request_headers", interruption, log)
}
return types.ActionContinue
}
func onHttpRequestBody(ctx wrapper.HttpContext, config WafConfig, body []byte, log wrapper.Log) types.Action {
// log.Info("[rinfx log] OnHttpRequestBody")
if ctx.GetContext("interruptionHandled").(bool) {
log.Error("OnHttpRequestBody, interruption already handled")
return types.ActionContinue
}
tx := ctx.GetContext("tx").(ctypes.Transaction)
if tx.IsRuleEngineOff() {
return types.ActionContinue
}
// Do not perform any action related to request body data if SecRequestBodyAccess is set to false
if !tx.IsRequestBodyAccessible() {
log.Info("Skipping request body inspection, SecRequestBodyAccess is off.")
// ProcessRequestBody is still performed for phase 2 rules, checking already populated variables
ctx.SetContext("processedRequestBody", true)
interruption, err := tx.ProcessRequestBody()
if err != nil {
log.Error("Failed to process request body")
return types.ActionContinue
}
if interruption != nil {
return handleInterruption(ctx, "http_request_body", interruption, log)
}
return types.ActionContinue
}
interruption, _, err := tx.WriteRequestBody(body)
if err != nil {
log.Error("Failed to write request body")
return types.ActionContinue
}
if interruption != nil {
return handleInterruption(ctx, "http_request_body", interruption, log)
}
ctx.SetContext("processedRequestBody", true)
interruption, err = tx.ProcessRequestBody()
if err != nil {
log.Error("Failed to process request body")
return types.ActionContinue
}
if interruption != nil {
return handleInterruption(ctx, "http_request_body", interruption, log)
}
return types.ActionContinue
}
func onHttpResponseHeaders(ctx wrapper.HttpContext, config WafConfig, log wrapper.Log) types.Action {
// log.Info("[rinfx log] OnHttpResponseHeaders")
if ctx.GetContext("interruptionHandled").(bool) {
log.Error("OnHttpResponseHeaders, interruption already handled")
return types.ActionContinue
}
tx := ctx.GetContext("tx").(ctypes.Transaction)
if tx.IsRuleEngineOff() {
return types.ActionContinue
}
// Requests without body won't call OnHttpRequestBody, but there are rules in the request body
// phase that still need to be executed. If they haven't been executed yet, now is the time.
if !ctx.GetContext("processedRequestBody").(bool) {
ctx.SetContext("processedRequestBody", true)
interruption, err := tx.ProcessRequestBody()
if err != nil {
log.Error("Failed to process request body")
return types.ActionContinue
}
if interruption != nil {
return handleInterruption(ctx, "http_response_headers", interruption, log)
}
}
status, err := proxywasm.GetHttpResponseHeader(":status")
if err != nil {
log.Error("Failed to get :status")
return types.ActionContinue
}
code, err := strconv.Atoi(status)
if err != nil {
code = 0
}
hs, err := proxywasm.GetHttpResponseHeaders()
if err != nil {
log.Error("Failed to get response headers")
return types.ActionContinue
}
for _, h := range hs {
tx.AddResponseHeader(h[0], h[1])
// log.Infof("[rinfx debug] ResponseHeaders %s: %s", h[0], h[1])
}
interruption := tx.ProcessResponseHeaders(code, ctx.GetContext("httpProtocol").(string))
if interruption != nil {
return handleInterruption(ctx, "http_response_headers", interruption, log)
}
return types.ActionContinue
}
func onHttpResponseBody(ctx wrapper.HttpContext, config WafConfig, body []byte, log wrapper.Log) types.Action {
// log.Info("[rinfx log] OnHttpResponseBody")
if ctx.GetContext("interruptionHandled").(bool) {
// At response body phase, proxy-wasm currently relies on emptying the response body as a way of
// interruption the response. See https://github.com/corazawaf/coraza-proxy-wasm/issues/26.
// If OnHttpResponseBody is called again and an interruption has already been raised, it means that
// we have to keep going with the sanitization of the response, emptying it.
// Sending the crafted HttpResponse with empty body, we don't expect to trigger OnHttpResponseBody
log.Warn("Response body interruption already handled, keeping replacing the body")
// Interruption happened, we don't want to send response body data
return replaceResponseBodyWhenInterrupted(log, replaceResponseBody)
}
tx := ctx.GetContext("tx").(ctypes.Transaction)
if tx.IsRuleEngineOff() {
return types.ActionContinue
}
// Do not perform any action related to response body data if SecResponseBodyAccess is set to false
if !tx.IsResponseBodyAccessible() {
log.Debug("Skipping response body inspection, SecResponseBodyAccess is off.")
// ProcessResponseBody is performed for phase 4 rules, checking already populated variables
ctx.SetContext("processedResponseBody", true)
interruption, err := tx.ProcessResponseBody()
if err != nil {
log.Error("Failed to process response body")
return types.ActionContinue
}
if interruption != nil {
// Proxy-wasm can not anymore deny the response. The best interruption is emptying the body
// Coraza Multiphase evaluation will help here avoiding late interruptions
return handleInterruption(ctx, "http_response_body", interruption, log)
}
return types.ActionContinue
}
interruption, _, err := tx.WriteResponseBody(body)
// log.Infof("[rinfx debug] ResponseBody %s", string(body))
if err != nil {
log.Error("Failed to write response body")
return types.ActionContinue
}
if interruption != nil {
return handleInterruption(ctx, "http_response_body", interruption, log)
}
// We have already sent response headers, an unauthorized response can not be sent anymore,
// but we can still drop the response to prevent leaking sensitive content.
// The error will also be logged by Coraza.
ctx.SetContext("processedResponseBody", true)
interruption, err = tx.ProcessResponseBody()
if err != nil {
log.Error("Failed to process response body")
return types.ActionContinue
}
if interruption != nil {
return handleInterruption(ctx, "http_response_body", interruption, log)
}
return types.ActionContinue
}
func onHttpStreamDone(ctx wrapper.HttpContext, config WafConfig, log wrapper.Log) {
// log.Info("[rinfx log] OnHttpStreamDone")
tx := ctx.GetContext("tx").(ctypes.Transaction)
if !tx.IsRuleEngineOff() {
// Responses without body won't call OnHttpResponseBody, but there are rules in the response body
// phase that still need to be executed. If they haven't been executed yet, now is the time.
if !ctx.GetContext("processedResponseBody").(bool) {
ctx.SetContext("processedResponseBody", true)
_, err := tx.ProcessResponseBody()
if err != nil {
log.Error("Failed to process response body")
}
}
}
tx.ProcessLogging()
_ = tx.Close()
log.Info("Finished")
}

View File

@@ -0,0 +1,253 @@
# -- Rule engine initialization ----------------------------------------------
# Enable Coraza, attaching it to every transaction. Use detection
# only to start with, because that minimises the chances of post-installation
# disruption.
#
SecRuleEngine On
# -- Request body handling ---------------------------------------------------
# Allow Coraza to access request bodies. If you don't, Coraza
# won't be able to see any POST parameters, which opens a large security
# hole for attackers to exploit.
#
SecRequestBodyAccess On
# Enable XML request body parser.
# Initiate XML Processor in case of xml content-type
#
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
# Enable JSON request body parser.
# Initiate JSON Processor in case of JSON content-type; change accordingly
# if your application does not use 'application/json'
#
SecRule REQUEST_HEADERS:Content-Type "^application/json" \
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Sample rule to enable JSON request body parser for more subtypes.
# Uncomment or adapt this rule if you want to engage the JSON
# Processor for "+json" subtypes
#
#SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \
# "id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Maximum request body size we will accept for buffering. If you support
# file uploads then the value given on the first line has to be as large
# as the largest file you are willing to accept. The second value refers
# to the size of data, with files excluded. You want to keep that value as
# low as practical.
#
SecRequestBodyLimit 13107200
SecRequestBodyInMemoryLimit 131072
SecRequestBodyNoFilesLimit 131072
# What to do if the request body size is above our configured limit.
# Keep in mind that this setting will automatically be set to ProcessPartial
# when SecRuleEngine is set to DetectionOnly mode in order to minimize
# disruptions when initially deploying Coraza.
#
SecRequestBodyLimitAction Reject
# Verify that we've correctly processed the request body.
# As a rule of thumb, when failing to process a request body
# you should reject the request (when deployed in blocking mode)
# or log a high-severity alert (when deployed in detection-only mode).
#
SecRule REQBODY_ERROR "!@eq 0" \
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
# By default be strict with what we accept in the multipart/form-data
# request body. If the rule below proves to be too strict for your
# environment consider changing it to detection-only. You are encouraged
# _not_ to remove it altogether.
#
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
"id:'200003',phase:2,t:none,log,deny,status:400, \
msg:'Multipart request body failed strict validation: \
PE %{REQBODY_PROCESSOR_ERROR}, \
BQ %{MULTIPART_BOUNDARY_QUOTED}, \
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
DB %{MULTIPART_DATA_BEFORE}, \
DA %{MULTIPART_DATA_AFTER}, \
HF %{MULTIPART_HEADER_FOLDING}, \
LF %{MULTIPART_LF_LINE}, \
SM %{MULTIPART_MISSING_SEMICOLON}, \
IQ %{MULTIPART_INVALID_QUOTING}, \
IP %{MULTIPART_INVALID_PART}, \
IH %{MULTIPART_INVALID_HEADER_FOLDING}, \
FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
# Did we see anything that might be a boundary?
#
# Here is a short description about the Coraza Multipart parser: the
# parser returns with value 0, if all "boundary-like" line matches with
# the boundary string which given in MIME header. In any other cases it returns
# with different value, eg. 1 or 2.
#
# The RFC 1341 descript the multipart content-type and its syntax must contains
# only three mandatory lines (above the content):
# * Content-Type: multipart/mixed; boundary=BOUNDARY_STRING
# * --BOUNDARY_STRING
# * --BOUNDARY_STRING--
#
# First line indicates, that this is a multipart content, second shows that
# here starts a part of the multipart content, third shows the end of content.
#
# If there are any other lines, which starts with "--", then it should be
# another boundary id - or not.
#
# After 3.0.3, there are two kinds of types of boundary errors: strict and permissive.
#
# If multipart content contains the three necessary lines with correct order, but
# there are one or more lines with "--", then parser returns with value 2 (non-zero).
#
# If some of the necessary lines (usually the start or end) misses, or the order
# is wrong, then parser returns with value 1 (also a non-zero).
#
# You can choose, which one is what you need. The example below contains the
# 'strict' mode, which means if there are any lines with start of "--", then
# Coraza blocked the content. But the next, commented example contains
# the 'permissive' mode, then you check only if the necessary lines exists in
# correct order. Whit this, you can enable to upload PEM files (eg "----BEGIN.."),
# or other text files, which contains eg. HTTP headers.
#
# The difference is only the operator - in strict mode (first) the content blocked
# in case of any non-zero value. In permissive mode (second, commented) the
# content blocked only if the value is explicit 1. If it 0 or 2, the content will
# allowed.
#
#
# See #1747 and #1924 for further information on the possible values for
# MULTIPART_UNMATCHED_BOUNDARY.
#
SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \
"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'"
# Some internal errors will set flags in TX and we will need to look for these.
# All of these are prefixed with "MSC_". The following flags currently exist:
#
# COR_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded.
#
SecRule TX:/^COR_/ "!@streq 0" \
"id:'200005',phase:2,t:none,deny,msg:'Coraza internal error flagged: %{MATCHED_VAR_NAME}'"
# -- Response body handling --------------------------------------------------
# Allow Coraza to access response bodies.
# You should have this directive enabled in order to identify errors
# and data leakage issues.
#
# Do keep in mind that enabling this directive does increases both
# memory consumption and response latency.
#
SecResponseBodyAccess On
# Which response MIME types do you want to inspect? You should adjust the
# configuration below to catch documents but avoid static files
# (e.g., images and archives).
#
SecResponseBodyMimeType text/plain text/html text/xml application/json
# Buffer response bodies of up to 512 KB in length.
SecResponseBodyLimit 524288
# What happens when we encounter a response body larger than the configured
# limit? By default, we process what we have and let the rest through.
# That's somewhat less secure, but does not break any legitimate pages.
#
SecResponseBodyLimitAction ProcessPartial
# -- Filesystem configuration ------------------------------------------------
# The location where Coraza stores temporary files (for example, when
# it needs to handle a file upload that is larger than the configured limit).
#
# This default setting is chosen due to all systems have /tmp available however,
# this is less than ideal. It is recommended that you specify a location that's private.
#
SecTmpDir /tmp/
# The location where Coraza will keep its persistent data. This default setting
# is chosen due to all systems have /tmp available however, it
# too should be updated to a place that other users can't access.
#
SecDataDir /tmp/
# -- File uploads handling configuration -------------------------------------
# The location where Coraza stores intercepted uploaded files. This
# location must be private to Coraza. You don't want other users on
# the server to access the files, do you?
#
#SecUploadDir /opt/coraza/var/upload/
# By default, only keep the files that were determined to be unusual
# in some way (by an external inspection script). For this to work you
# will also need at least one file inspection rule.
#
#SecUploadKeepFiles RelevantOnly
# Uploaded files are by default created with permissions that do not allow
# any other user to access them. You may need to relax that if you want to
# interface Coraza to an external program (e.g., an anti-virus).
#
#SecUploadFileMode 0600
# -- Debug log configuration -------------------------------------------------
# Default debug log path
# Debug levels:
# 0: No logging (least verbose)
# 1: Error
# 2: Warn
# 3: Info
# 4-8: Debug
# 9: Trace (most verbose)
# Most logging has not been implemented because it will be replaced with
# advanced rule profiling options
#SecDebugLog /opt/coraza/var/log/debug.log
SecDebugLogLevel 3
# -- Audit log configuration -------------------------------------------------
# Log the transactions that are marked by a rule, as well as those that
# trigger a server error (determined by a 5xx or 4xx, excluding 404,
# level response status codes).
#
SecAuditEngine On
SecAuditLogRelevantStatus "^(?:(5|4)(0|1)[0-9])$"
# Log everything we know about a transaction.
SecAuditLogParts ABIJDEFHZ
# Use a single file for logging. This is much easier to look at, but
# assumes that you will use the audit log only occasionally.
#
SecAuditLogType Serial
# -- Miscellaneous -----------------------------------------------------------
# Use the most commonly used application/x-www-form-urlencoded parameter
# separator. There's probably only one application somewhere that uses
# something else so don't expect to change this value.
#
SecArgumentSeparator &
# Settle on version 0 (zero) cookies, as that is what most applications
# use. Using an incorrect cookie version may open your installation to
# evasion attacks (against the rules that examine named cookies).
#
SecCookieFormat 0

View File

@@ -0,0 +1,253 @@
# -- Rule engine initialization ----------------------------------------------
# Enable Coraza, attaching it to every transaction. Use detection
# only to start with, because that minimises the chances of post-installation
# disruption.
#
SecRuleEngine DetectionOnly
# -- Request body handling ---------------------------------------------------
# Allow Coraza to access request bodies. If you don't, Coraza
# won't be able to see any POST parameters, which opens a large security
# hole for attackers to exploit.
#
SecRequestBodyAccess On
# Enable XML request body parser.
# Initiate XML Processor in case of xml content-type
#
SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
# Enable JSON request body parser.
# Initiate JSON Processor in case of JSON content-type; change accordingly
# if your application does not use 'application/json'
#
SecRule REQUEST_HEADERS:Content-Type "^application/json" \
"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Sample rule to enable JSON request body parser for more subtypes.
# Uncomment or adapt this rule if you want to engage the JSON
# Processor for "+json" subtypes
#
#SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \
# "id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON"
# Maximum request body size we will accept for buffering. If you support
# file uploads then the value given on the first line has to be as large
# as the largest file you are willing to accept. The second value refers
# to the size of data, with files excluded. You want to keep that value as
# low as practical.
#
SecRequestBodyLimit 13107200
SecRequestBodyInMemoryLimit 131072
SecRequestBodyNoFilesLimit 131072
# What to do if the request body size is above our configured limit.
# Keep in mind that this setting will automatically be set to ProcessPartial
# when SecRuleEngine is set to DetectionOnly mode in order to minimize
# disruptions when initially deploying Coraza.
#
SecRequestBodyLimitAction Reject
# Verify that we've correctly processed the request body.
# As a rule of thumb, when failing to process a request body
# you should reject the request (when deployed in blocking mode)
# or log a high-severity alert (when deployed in detection-only mode).
#
SecRule REQBODY_ERROR "!@eq 0" \
"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2"
# By default be strict with what we accept in the multipart/form-data
# request body. If the rule below proves to be too strict for your
# environment consider changing it to detection-only. You are encouraged
# _not_ to remove it altogether.
#
SecRule MULTIPART_STRICT_ERROR "!@eq 0" \
"id:'200003',phase:2,t:none,log,deny,status:400, \
msg:'Multipart request body failed strict validation: \
PE %{REQBODY_PROCESSOR_ERROR}, \
BQ %{MULTIPART_BOUNDARY_QUOTED}, \
BW %{MULTIPART_BOUNDARY_WHITESPACE}, \
DB %{MULTIPART_DATA_BEFORE}, \
DA %{MULTIPART_DATA_AFTER}, \
HF %{MULTIPART_HEADER_FOLDING}, \
LF %{MULTIPART_LF_LINE}, \
SM %{MULTIPART_MISSING_SEMICOLON}, \
IQ %{MULTIPART_INVALID_QUOTING}, \
IP %{MULTIPART_INVALID_PART}, \
IH %{MULTIPART_INVALID_HEADER_FOLDING}, \
FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'"
# Did we see anything that might be a boundary?
#
# Here is a short description about the Coraza Multipart parser: the
# parser returns with value 0, if all "boundary-like" line matches with
# the boundary string which given in MIME header. In any other cases it returns
# with different value, eg. 1 or 2.
#
# The RFC 1341 descript the multipart content-type and its syntax must contains
# only three mandatory lines (above the content):
# * Content-Type: multipart/mixed; boundary=BOUNDARY_STRING
# * --BOUNDARY_STRING
# * --BOUNDARY_STRING--
#
# First line indicates, that this is a multipart content, second shows that
# here starts a part of the multipart content, third shows the end of content.
#
# If there are any other lines, which starts with "--", then it should be
# another boundary id - or not.
#
# After 3.0.3, there are two kinds of types of boundary errors: strict and permissive.
#
# If multipart content contains the three necessary lines with correct order, but
# there are one or more lines with "--", then parser returns with value 2 (non-zero).
#
# If some of the necessary lines (usually the start or end) misses, or the order
# is wrong, then parser returns with value 1 (also a non-zero).
#
# You can choose, which one is what you need. The example below contains the
# 'strict' mode, which means if there are any lines with start of "--", then
# Coraza blocked the content. But the next, commented example contains
# the 'permissive' mode, then you check only if the necessary lines exists in
# correct order. Whit this, you can enable to upload PEM files (eg "----BEGIN.."),
# or other text files, which contains eg. HTTP headers.
#
# The difference is only the operator - in strict mode (first) the content blocked
# in case of any non-zero value. In permissive mode (second, commented) the
# content blocked only if the value is explicit 1. If it 0 or 2, the content will
# allowed.
#
#
# See #1747 and #1924 for further information on the possible values for
# MULTIPART_UNMATCHED_BOUNDARY.
#
SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \
"id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'"
# Some internal errors will set flags in TX and we will need to look for these.
# All of these are prefixed with "MSC_". The following flags currently exist:
#
# COR_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded.
#
SecRule TX:/^COR_/ "!@streq 0" \
"id:'200005',phase:2,t:none,deny,msg:'Coraza internal error flagged: %{MATCHED_VAR_NAME}'"
# -- Response body handling --------------------------------------------------
# Allow Coraza to access response bodies.
# You should have this directive enabled in order to identify errors
# and data leakage issues.
#
# Do keep in mind that enabling this directive does increases both
# memory consumption and response latency.
#
SecResponseBodyAccess On
# Which response MIME types do you want to inspect? You should adjust the
# configuration below to catch documents but avoid static files
# (e.g., images and archives).
#
SecResponseBodyMimeType text/plain text/html text/xml
# Buffer response bodies of up to 512 KB in length.
SecResponseBodyLimit 524288
# What happens when we encounter a response body larger than the configured
# limit? By default, we process what we have and let the rest through.
# That's somewhat less secure, but does not break any legitimate pages.
#
SecResponseBodyLimitAction ProcessPartial
# -- Filesystem configuration ------------------------------------------------
# The location where Coraza stores temporary files (for example, when
# it needs to handle a file upload that is larger than the configured limit).
#
# This default setting is chosen due to all systems have /tmp available however,
# this is less than ideal. It is recommended that you specify a location that's private.
#
SecTmpDir /tmp/
# The location where Coraza will keep its persistent data. This default setting
# is chosen due to all systems have /tmp available however, it
# too should be updated to a place that other users can't access.
#
SecDataDir /tmp/
# -- File uploads handling configuration -------------------------------------
# The location where Coraza stores intercepted uploaded files. This
# location must be private to Coraza. You don't want other users on
# the server to access the files, do you?
#
#SecUploadDir /opt/coraza/var/upload/
# By default, only keep the files that were determined to be unusual
# in some way (by an external inspection script). For this to work you
# will also need at least one file inspection rule.
#
#SecUploadKeepFiles RelevantOnly
# Uploaded files are by default created with permissions that do not allow
# any other user to access them. You may need to relax that if you want to
# interface Coraza to an external program (e.g., an anti-virus).
#
#SecUploadFileMode 0600
# -- Debug log configuration -------------------------------------------------
# Default debug log path
# Debug levels:
# 0: No logging (least verbose)
# 1: Error
# 2: Warn
# 3: Info
# 4-8: Debug
# 9: Trace (most verbose)
# Most logging has not been implemented because it will be replaced with
# advanced rule profiling options
#SecDebugLog /opt/coraza/var/log/debug.log
#SecDebugLogLevel 3
# -- Audit log configuration -------------------------------------------------
# Log the transactions that are marked by a rule, as well as those that
# trigger a server error (determined by a 5xx or 4xx, excluding 404,
# level response status codes).
#
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:(5|4)(0|1)[0-9])$"
# Log everything we know about a transaction.
SecAuditLogParts ABIJDEFHZ
# Use a single file for logging. This is much easier to look at, but
# assumes that you will use the audit log only occasionally.
#
SecAuditLogType Serial
# -- Miscellaneous -----------------------------------------------------------
# Use the most commonly used application/x-www-form-urlencoded parameter
# separator. There's probably only one application somewhere that uses
# something else so don't expect to change this value.
#
SecArgumentSeparator &
# Settle on version 0 (zero) cookies, as that is what most applications
# use. Using an incorrect cookie version may open your installation to
# evasion attacks (against the rules that examine named cookies).
#
SecCookieFormat 0

View File

@@ -0,0 +1,727 @@
# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.4.0.0-rc1
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2022 Core Rule Set project. All rights reserved.
#
# The OWASP ModSecurity Core Rule Set is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------
#
# -- [[ Introduction ]] --------------------------------------------------------
#
# The OWASP ModSecurity Core Rule Set (CRS) is a set of generic attack
# detection rules that provide a base level of protection for any web
# application. They are written for the open source, cross-platform
# ModSecurity Web Application Firewall.
#
# See also:
# https://coreruleset.org/
# https://github.com/coreruleset/coreruleset
# https://owasp.org/www-project-modsecurity-core-rule-set/
#
#
# -- [[ System Requirements ]] -------------------------------------------------
#
# CRS requires ModSecurity version 2.8.0 or above.
# We recommend to always use the newest ModSecurity version.
#
# The configuration directives/settings in this file are used to control
# the OWASP ModSecurity CRS. These settings do **NOT** configure the main
# ModSecurity settings (modsecurity.conf) such as SecRuleEngine,
# SecRequestBodyAccess, SecAuditEngine, SecDebugLog, and XML processing.
#
# The CRS assumes that modsecurity.conf has been loaded. It is bundled with
# ModSecurity. If you don't have it, you can get it from:
# 2.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v2/master/modsecurity.conf-recommended
# 3.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
#
# The order of file inclusion in your webserver configuration should always be:
# 1. modsecurity.conf
# 2. crs-setup.conf (this file)
# 3. rules/*.conf (the CRS rule files)
#
# Please refer to the INSTALL file for detailed installation instructions.
#
#
# -- [[ Mode of Operation: Anomaly Scoring vs. Self-Contained ]] ---------------
#
# The CRS can run in two modes:
#
# -- [[ Anomaly Scoring Mode (default) ]] --
# In CRS3, anomaly mode is the default and recommended mode, since it gives the
# most accurate log information and offers the most flexibility in setting your
# blocking policies. It is also called "collaborative detection mode".
# In this mode, each matching rule increases an 'anomaly score'.
# At the conclusion of the inbound rules, and again at the conclusion of the
# outbound rules, the anomaly score is checked, and the blocking evaluation
# rules apply a disruptive action, by default returning an error 403.
#
# -- [[ Self-Contained Mode ]] --
# In this mode, rules apply an action instantly. This was the CRS2 default.
# It can lower resource usage, at the cost of less flexibility in blocking policy
# and less informative audit logs (only the first detected threat is logged).
# Rules inherit the disruptive action that you specify (i.e. deny, drop, etc).
# The first rule that matches will execute this action. In most cases this will
# cause evaluation to stop after the first rule has matched, similar to how many
# IDSs function.
#
# -- [[ Alert Logging Control ]] --
# In the mode configuration, you must also adjust the desired logging options.
# There are three common options for dealing with logging. By default CRS enables
# logging to the webserver error log (or Event viewer) plus detailed logging to
# the ModSecurity audit log (configured under SecAuditLog in modsecurity.conf).
#
# - To log to both error log and ModSecurity audit log file, use: "log,auditlog"
# - To log *only* to the ModSecurity audit log file, use: "nolog,auditlog"
# - To log *only* to the error log file, use: "log,noauditlog"
#
# Examples for the various modes follow.
# You must leave one of the following options enabled.
# Note that you must specify the same line for phase:1 and phase:2.
#
# Default: Anomaly Scoring mode, log to error log, log to ModSecurity audit log
# - By default, offending requests are blocked with an error 403 response.
# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
SecDefaultAction "phase:1,log,auditlog,pass"
SecDefaultAction "phase:2,log,auditlog,pass"
# Example: Anomaly Scoring mode, log only to ModSecurity audit log
# - By default, offending requests are blocked with an error 403 response.
# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
# SecDefaultAction "phase:1,nolog,auditlog,pass"
# SecDefaultAction "phase:2,nolog,auditlog,pass"
# Example: Self-contained mode, return error 403 on blocking
# - In this configuration the default disruptive action becomes 'deny'. After a
# rule triggers, it will stop processing the request and return an error 403.
# - You can also use a different error status, such as 404, 406, et cetera.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
# SecDefaultAction "phase:1,log,auditlog,deny,status:403"
# SecDefaultAction "phase:2,log,auditlog,deny,status:403"
# Example: Self-contained mode, redirect back to homepage on blocking
# - In this configuration the 'tag' action includes the Host header data in the
# log. This helps to identify which virtual host triggered the rule (if any).
# - Note that this might cause redirect loops in some situations; for example
# if a Cookie or User-Agent header is blocked, it will also be blocked when
# the client subsequently tries to access the homepage. You can also redirect
# to another custom URL.
# SecDefaultAction "phase:1,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
# SecDefaultAction "phase:2,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
#
# -- [[ Paranoia Level Initialization ]] ---------------------------------------
#
# The Paranoia Level (PL) setting allows you to choose the desired level
# of rule checks that will add to your anomaly scores.
#
# With each paranoia level increase, the CRS enables additional rules
# giving you a higher level of security. However, higher paranoia levels
# also increase the possibility of blocking some legitimate traffic due to
# false alarms (also named false positives or FPs). If you use higher
# paranoia levels, it is likely that you will need to add some exclusion
# rules for certain requests and applications receiving complex input.
#
# - A paranoia level of 1 is default. In this level, most core rules
# are enabled. PL1 is advised for beginners, installations
# covering many different sites and applications, and for setups
# with standard security requirements.
# At PL1 you should face FPs rarely. If you encounter FPs, please
# open an issue on the CRS GitHub site and don't forget to attach your
# complete Audit Log record for the request with the issue.
# - Paranoia level 2 includes many extra rules, for instance enabling
# many regexp-based SQL and XSS injection protections, and adding
# extra keywords checked for code injections. PL2 is advised
# for moderate to experienced users desiring more complete coverage
# and for installations with elevated security requirements.
# PL2 comes with some FPs which you need to handle.
# - Paranoia level 3 enables more rules and keyword lists, and tweaks
# limits on special characters used. PL3 is aimed at users experienced
# at the handling of FPs and at installations with a high security
# requirement.
# - Paranoia level 4 further restricts special characters.
# The highest level is advised for experienced users protecting
# installations with very high security requirements. Running PL4 will
# likely produce a very high number of FPs which have to be
# treated before the site can go productive.
#
# All rules will log their PL to the audit log;
# example: [tag "paranoia-level/2"]. This allows you to deduct from the
# audit log how the WAF behavior is affected by paranoia level.
#
# It is important to also look into the variable
# tx.enforce_bodyproc_urlencoded (Enforce Body Processor URLENCODED)
# defined below. Enabling it closes a possible bypass of CRS.
#
# Uncomment this rule to change the default:
#
SecAction \
"id:900000,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.blocking_paranoia_level=1"
# It is possible to execute rules from a higher paranoia level but not include
# them in the anomaly scoring. This allows you to take a well-tuned system on
# paranoia level 1 and add rules from paranoia level 2 without having to fear
# the new rules would lead to false positives that raise your score above the
# threshold.
# This optional feature is enabled by uncommenting the following rule and
# setting the tx.detection_paranoia_level.
# Technically, rules up to the level defined in tx.detection_paranoia_level
# will be executed, but only the rules up to tx.blocking_paranoia_level affect the
# anomaly scores.
# By default, tx.detection_paranoia_level is set to tx.blocking_paranoia_level.
# tx.detection_paranoia_level must not be lower than tx.blocking_paranoia_level.
#
# Please notice that setting tx.detection_paranoia_level to a higher paranoia
# level results in a performance impact that is equally high as setting
# tx.blocking_paranoia_level to said level.
#
#SecAction \
# "id:900001,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.detection_paranoia_level=1"
#
# -- [[ Enforce Body Processor URLENCODED ]] -----------------------------------
#
# ModSecurity selects the body processor based on the Content-Type request
# header. But clients are not always setting the Content-Type header for their
# request body payloads. This will leave ModSecurity with limited vision into
# the payload. The variable tx.enforce_bodyproc_urlencoded lets you force the
# URLENCODED body processor in these situations. This is off by default, as it
# implies a change of the behaviour of ModSecurity beyond CRS (the body
# processor applies to all rules, not only CRS) and because it may lead to
# false positives already on paranoia level 1. However, enabling this variable
# closes a possible bypass of CRS so it should be considered.
#
# Uncomment this rule to change the default:
#
#SecAction \
# "id:900010,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.enforce_bodyproc_urlencoded=1"
#
# -- [[ Anomaly Scoring Mode Severity Levels ]] --------------------------------
#
# Each rule in the CRS has an associated severity level.
# These are the default scoring points for each severity level.
# These settings will be used to increment the anomaly score if a rule matches.
# You may adjust these points to your liking, but this is usually not needed.
#
# - CRITICAL severity: Anomaly Score of 5.
# Mostly generated by the application attack rules (93x and 94x files).
# - ERROR severity: Anomaly Score of 4.
# Generated mostly from outbound leakage rules (95x files).
# - WARNING severity: Anomaly Score of 3.
# Generated mostly by malicious client rules (91x files).
# - NOTICE severity: Anomaly Score of 2.
# Generated mostly by the protocol rules (92x files).
#
# In anomaly mode, these scores are cumulative.
# So it's possible for a request to hit multiple rules.
#
# (Note: In this file, we use 'phase:1' to set CRS configuration variables.
# In general, 'phase:request' is used. However, we want to make absolutely sure
# that all configuration variables are set before the CRS rules are processed.)
#
SecAction \
"id:900100,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.critical_anomaly_score=5,\
setvar:tx.error_anomaly_score=4,\
setvar:tx.warning_anomaly_score=3,\
setvar:tx.notice_anomaly_score=2"
#
# -- [[ Anomaly Scoring Mode Blocking Threshold Levels ]] ----------------------
#
# Here, you can specify at which cumulative anomaly score an inbound request,
# or outbound response, gets blocked.
#
# Most detected inbound threats will give a critical score of 5.
# Smaller violations, like violations of protocol/standards, carry lower scores.
#
# [ At default value ]
# If you keep the blocking thresholds at the defaults, the CRS will work
# similarly to previous CRS versions: a single critical rule match will cause
# the request to be blocked and logged.
#
# [ Using higher values ]
# If you want to make the CRS less sensitive, you can increase the blocking
# thresholds, for instance to 7 (which would require multiple rule matches
# before blocking) or 10 (which would require at least two critical alerts - or
# a combination of many lesser alerts), or even higher. However, increasing the
# thresholds might cause some attacks to bypass the CRS rules or your policies.
#
# [ New deployment strategy: Starting high and decreasing ]
# It is a common practice to start a fresh CRS installation with elevated
# anomaly scoring thresholds (>100) and then lower the limits as your
# confidence in the setup grows. You may also look into the Sampling
# Percentage section below for a different strategy to ease into a new
# CRS installation.
#
# [ Anomaly Threshold / Paranoia Level Quadrant ]
#
# High Anomaly Limit | High Anomaly Limit
# Low Paranoia Level | High Paranoia Level
# -> Fresh Site | -> Experimental Site
# ------------------------------------------------------
# Low Anomaly Limit | Low Anomaly Limit
# Low Paranoia Level | High Paranoia Level
# -> Standard Site | -> High Security Site
#
# Uncomment this rule to change the defaults:
#
#SecAction \
# "id:900110,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.inbound_anomaly_score_threshold=5,\
# setvar:tx.outbound_anomaly_score_threshold=4"
#
# -- [[ Application Specific Rule Exclusions ]] --------------------------------
#
# CRS 3.x contained exclusion packages to tweak the CRS for use with common
# web applications, lowering the number of false positives.
#
# In CRS 4, these are no longer part of the CRS itself, but they are available
# as "CRS plugins". Some plugins improve support for web applications, and others
# may bring new functionality. Plugins are not installed by default, but can be
# downloaded from the plugin registry:
# https://github.com/coreruleset/plugin-registry
#
# For detailed information about using and installing plugins, please see:
# https://coreruleset.org/docs/concepts/plugins/
#
# -- [[ Anomaly Score Reporting Level ]] ---------------------------------------
#
# When a request is blocked due to the anomaly score meeting or exceeding the
# anomaly threshold then the blocking rule will also report the anomaly score.
# This applies to the separate inbound and outbound anomaly scores.
#
# In phase 5, there are additional rules that can perform additional reporting
# of anomaly scores with a verbosity that depends on the reporting level defined
# below.
#
# By setting the reporting level you control whether you want additional
# reporting beyond the blocking rule or not and, if yes, which requests should
# be covered. The higher the reporting level, the more verbose the reporting is.
#
# There are 6 reporting levels:
#
# 0 - Reporting disabled
# 1 - Reporting for requests with a blocking anomaly score >= a threshold
# 2 - Reporting for requests with a detection anomaly score >= a threshold
# 3 - Reporting for requests with a blocking anomaly score greater than 0
# 4 - Reporting for requests with a detection anomaly score greater than 0
# 5 - Reporting for all requests
#
# Note: Reporting levels 1 and 2 make it possible to differentiate between
# requests that are blocked and requests that are *not* blocked but would have
# been blocked if the blocking PL was equal to detection PL. This may be useful
# for certain FP tuning methodologies, for example moving to a higher PL.
#
# A value of 5 can be useful on platforms where you are interested in logging
# non-scoring requests, yet it is not possible to report this information in
# the request/access log. This applies to Nginx, for example.
#
#SecAction \
# "id:900115,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.reporting_level=4"
#
# -- [[ Early Anomaly Scoring Mode Blocking ]] ------------------------------
#
# The anomaly scores for the request and the responses are generally summed up
# and evaluated at the end of phase:2 and at the end of phase:4 respectively.
# However, it is possible to enable an early evaluation of these anomaly scores
# at the end of phase:1 and at the end of phase:3.
#
# If a request (or a response) hits the anomaly threshold in this early
# evaluation, then blocking happens immediately (if blocking is enabled) and
# the phase 2 (and phase 4 respectively) will no longer be executed.
#
# Enable the rule 900120 that sets the variable tx.early_blocking to 1 in order
# to enable early blocking. The variable tx.early_blocking is set to 0 by
# default. Early blocking is thus disabled by default.
#
# Please note that early blocking will hide potential alerts from you. This
# means that a payload that would appear in an alert in phase 2 (or phase 4)
# does not get evaluated if the request is being blocked early. So when you
# disabled early blocking again at some point in the future, then new alerts
# from phase 2 might pop up.
SecAction \
"id:900120,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.early_blocking=1"
#
# -- [[ HTTP Policy Settings ]] ------------------------------------------------
#
# This section defines your policies for the HTTP protocol, such as:
# - allowed HTTP versions, HTTP methods, allowed request Content-Types
# - forbidden file extensions (e.g. .bak, .sql) and request headers (e.g. Proxy)
#
# These variables are used in the following rule files:
# - REQUEST-911-METHOD-ENFORCEMENT.conf
# - REQUEST-920-PROTOCOL-ENFORCEMENT.conf
# HTTP methods that a client is allowed to use.
# Default: GET HEAD POST OPTIONS
# Example: for RESTful APIs, add the following methods: PUT PATCH DELETE
# Example: for WebDAV, add the following methods: CHECKOUT COPY DELETE LOCK
# MERGE MKACTIVITY MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK
# Uncomment this rule to change the default.
#SecAction \
# "id:900200,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.allowed_methods=GET HEAD POST OPTIONS'"
# Content-Types that a client is allowed to send in a request.
# Default: |application/x-www-form-urlencoded| |multipart/form-data| |multipart/related|
# |text/xml| |application/xml| |application/soap+xml| |application/json|
# |application/cloudevents+json| |application/cloudevents-batch+json|
#
# Please note, that the rule where CRS uses this variable (920420) evaluates it with operator
# `@within`, which is case sensitive, but uses t:lowercase. You must add your whole custom
# Content-Type with lowercase.
#
# Bypass Warning: some applications may not rely on the content-type request header in order
# to parse the request body. This could make an attacker able to send malicious URLENCODED/JSON/XML
# payloads without being detected by the WAF. Allowing request content-type that doesn't activate any
# body processor (for example: "text/plain", "application/x-amf", "application/octet-stream", etc..)
# could lead to a WAF bypass. For example, a malicious JSON payload submitted with a "text/plain"
# content type may still be interpreted as JSON by a backend application but would not trigger the
# JSON body parser at the WAF, leading to a bypass.
#
# To prevent blocking request with not allowed content-type by default, you can create an exclusion
# rule that removes rule 920420. For example:
#SecRule REQUEST_HEADERS:Content-Type "@rx ^text/plain" \
# "id:1234,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# ctl:ruleRemoveById=920420,\
# chain"
# SecRule REQUEST_URI "@rx ^/foo/bar" "t:none"
#
# Uncomment this rule to change the default.
#
#SecAction \
# "id:900220,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json|'"
# Allowed HTTP versions.
# Default: HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0
# Example for legacy clients: HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0
# Note that some web server versions use 'HTTP/2', some 'HTTP/2.0', so
# we include both version strings by default.
# Uncomment this rule to change the default.
#SecAction \
# "id:900230,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.allowed_http_versions=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0'"
# Forbidden file extensions.
# Guards against unintended exposure of development/configuration files.
# Default: .asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/
# Example: .bak/ .config/ .conf/ .db/ .ini/ .log/ .old/ .pass/ .pdb/ .rdb/ .sql/
# Note that .axd was removed due to false positives (see PR 1925).
#
# To additionally guard against configuration/install archive files from being
# accidentally exposed, common archive file extensions can be added to the
# restricted extensions list. An example list of common archive file extensions
# is presented below:
# .7z/ .br/ .bz/ .bz2/ .cab/ .cpio/ .gz/ .img/ .iso/ .jar/ .rar/ .tar/ .tbz2/ .tgz/ .txz/ .xz/ .zip/ .zst/
# (Source: https://en.wikipedia.org/wiki/List_of_archive_formats)
#
# Uncomment this rule to change the default.
#SecAction \
# "id:900240,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/'"
# Forbidden request headers.
# Header names should be lowercase, enclosed by /slashes/ as delimiters.
# Default: /accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/
#
# Note: Accept-Charset is a deprecated header that should not be used by clients and
# ignored by servers. It can be used for a response WAF bypass, by asking for a charset
# that the WAF cannot decode.
# Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
#
# Note: Content-Encoding is used to list any encodings that have been applied to the
# original payload. It is only used for compression, which isn't supported by CRS by
# default since it blocks newlines and null bytes inside the request body. Most
# compression algorithms require at least null bytes per RFC. Blocking it shouldn't
# break anything and increases security since ModSecurity is incapable of properly
# scanning compressed request bodies.
#
# Note: Blocking Proxy header prevents 'httpoxy' vulnerability: https://httpoxy.org
#
# Note: Blocking the x-http-method-override,x-http-method and x-method-override headers
# prevents attacks as described here: https://www.sidechannel.blog/en/http-method-override-what-it-is-and-how-a-pentester-can-use-it
#
# Uncomment this rule to change the default.
#SecAction \
# "id:900250,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.restricted_headers=/accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/ /x-http-method-override/ /x-http-method/ /x-method-override/'"
# Content-Types charsets that a client is allowed to send in a request.
# The content-types are enclosed by |pipes| as delimiters to guarantee exact matches.
# Default: |utf-8| |iso-8859-1| |iso-8859-15| |windows-1252|
# Uncomment this rule to change the default.
#SecAction \
# "id:900280,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.allowed_request_content_type_charset=|utf-8| |iso-8859-1| |iso-8859-15| |windows-1252|'"
#
# -- [[ HTTP Argument/Upload Limits ]] -----------------------------------------
#
# Here you can define optional limits on HTTP get/post parameters and uploads.
# This can help to prevent application specific DoS attacks.
#
# These values are checked in REQUEST-920-PROTOCOL-ENFORCEMENT.conf.
# Beware of blocking legitimate traffic when enabling these limits.
#
# Block request if number of arguments is too high
# Default: unlimited
# Example: 255
# Uncomment this rule to set a limit.
#SecAction \
# "id:900300,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.max_num_args=255"
# Block request if the length of any argument name is too high
# Default: unlimited
# Example: 100
# Uncomment this rule to set a limit.
#SecAction \
# "id:900310,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.arg_name_length=100"
# Block request if the length of any argument value is too high
# Default: unlimited
# Example: 400
# Uncomment this rule to set a limit.
#SecAction \
# "id:900320,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.arg_length=400"
# Block request if the total length of all combined arguments is too high
# Default: unlimited
# Example: 64000
# Uncomment this rule to set a limit.
#SecAction \
# "id:900330,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.total_arg_length=64000"
# Block request if the file size of any individual uploaded file is too high
# Default: unlimited
# Example: 1048576
# Uncomment this rule to set a limit.
#SecAction \
# "id:900340,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.max_file_size=1048576"
# Block request if the total size of all combined uploaded files is too high
# Default: unlimited
# Example: 1048576
# Uncomment this rule to set a limit.
#SecAction \
# "id:900350,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.combined_file_sizes=1048576"
#
# -- [[ Easing In / Sampling Percentage ]] -------------------------------------
#
# Adding the Core Rule Set to an existing productive site can lead to false
# positives, unexpected performance issues and other undesired side effects.
#
# It can be beneficial to test the water first by enabling the CRS for a
# limited number of requests only and then, when you have solved the issues (if
# any) and you have confidence in the setup, to raise the ratio of requests
# being sent into the ruleset.
#
# Adjust the percentage of requests that are funnelled into the Core Rules by
# setting TX.sampling_percentage below. The default is 100, meaning that every
# request gets checked by the CRS. The selection of requests, which are going
# to be checked, is based on a pseudo random number generated by ModSecurity.
#
# If a request is allowed to pass without being checked by the CRS, there is no
# entry in the audit log (for performance reasons), but an error log entry is
# written. If you want to disable the error log entry, then issue the
# following directive somewhere after the inclusion of the CRS
# (E.g., RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf).
#
#SecRuleUpdateActionById 901450 "nolog"
#
# ATTENTION: If this TX.sampling_percentage is below 100, then some of the
# requests will bypass the Core Rules completely and you lose the ability to
# protect your service with ModSecurity.
#
# Uncomment this rule to enable this feature:
#
#SecAction \
# "id:900400,\
# phase:1,\
# pass,\
# nolog,\
# setvar:tx.sampling_percentage=100"
#
# -- [[ Check UTF-8 encoding ]] ------------------------------------------------
#
# The CRS can optionally check request contents for invalid UTF-8 encoding.
# We only want to apply this check if UTF-8 encoding is actually used by the
# site; otherwise it will result in false positives.
#
# Uncomment this rule to use this feature:
#
#SecAction \
# "id:900950,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.crs_validate_utf8_encoding=1"
#
# -- [[ Collection timeout ]] --------------------------------------------------
#
# Set the SecCollectionTimeout directive from the ModSecurity default (1 hour)
# to a lower setting which is appropriate to most sites.
# This increases performance by cleaning out stale collection (block) entries.
#
# This value should be greater than or equal to any block durations or timeouts
# set by plugins that make use of ModSecurity's persistent collections (e.g. the
# DoS protection and IP reputation plugins).
#
# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#SecCollectionTimeout
# Please keep this directive uncommented.
# Default: 600 (10 minutes)
SecCollectionTimeout 600
#
# -- [[ End of setup ]] --------------------------------------------------------
#
# The CRS checks the tx.crs_setup_version variable to ensure that the setup
# has been loaded. If you are not planning to use this setup template,
# you must manually set the tx.crs_setup_version variable before including
# the CRS rules/* files.
#
# The variable is a numerical representation of the CRS version number.
# E.g., v3.0.0 is represented as 300.
#
SecAction \
"id:900990,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.crs_setup_version=400"

View File

@@ -0,0 +1,727 @@
# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.4.0.0-rc1
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2022 Core Rule Set project. All rights reserved.
#
# The OWASP ModSecurity Core Rule Set is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------
#
# -- [[ Introduction ]] --------------------------------------------------------
#
# The OWASP ModSecurity Core Rule Set (CRS) is a set of generic attack
# detection rules that provide a base level of protection for any web
# application. They are written for the open source, cross-platform
# ModSecurity Web Application Firewall.
#
# See also:
# https://coreruleset.org/
# https://github.com/coreruleset/coreruleset
# https://owasp.org/www-project-modsecurity-core-rule-set/
#
#
# -- [[ System Requirements ]] -------------------------------------------------
#
# CRS requires ModSecurity version 2.8.0 or above.
# We recommend to always use the newest ModSecurity version.
#
# The configuration directives/settings in this file are used to control
# the OWASP ModSecurity CRS. These settings do **NOT** configure the main
# ModSecurity settings (modsecurity.conf) such as SecRuleEngine,
# SecRequestBodyAccess, SecAuditEngine, SecDebugLog, and XML processing.
#
# The CRS assumes that modsecurity.conf has been loaded. It is bundled with
# ModSecurity. If you don't have it, you can get it from:
# 2.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v2/master/modsecurity.conf-recommended
# 3.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended
#
# The order of file inclusion in your webserver configuration should always be:
# 1. modsecurity.conf
# 2. crs-setup.conf (this file)
# 3. rules/*.conf (the CRS rule files)
#
# Please refer to the INSTALL file for detailed installation instructions.
#
#
# -- [[ Mode of Operation: Anomaly Scoring vs. Self-Contained ]] ---------------
#
# The CRS can run in two modes:
#
# -- [[ Anomaly Scoring Mode (default) ]] --
# In CRS3, anomaly mode is the default and recommended mode, since it gives the
# most accurate log information and offers the most flexibility in setting your
# blocking policies. It is also called "collaborative detection mode".
# In this mode, each matching rule increases an 'anomaly score'.
# At the conclusion of the inbound rules, and again at the conclusion of the
# outbound rules, the anomaly score is checked, and the blocking evaluation
# rules apply a disruptive action, by default returning an error 403.
#
# -- [[ Self-Contained Mode ]] --
# In this mode, rules apply an action instantly. This was the CRS2 default.
# It can lower resource usage, at the cost of less flexibility in blocking policy
# and less informative audit logs (only the first detected threat is logged).
# Rules inherit the disruptive action that you specify (i.e. deny, drop, etc).
# The first rule that matches will execute this action. In most cases this will
# cause evaluation to stop after the first rule has matched, similar to how many
# IDSs function.
#
# -- [[ Alert Logging Control ]] --
# In the mode configuration, you must also adjust the desired logging options.
# There are three common options for dealing with logging. By default CRS enables
# logging to the webserver error log (or Event viewer) plus detailed logging to
# the ModSecurity audit log (configured under SecAuditLog in modsecurity.conf).
#
# - To log to both error log and ModSecurity audit log file, use: "log,auditlog"
# - To log *only* to the ModSecurity audit log file, use: "nolog,auditlog"
# - To log *only* to the error log file, use: "log,noauditlog"
#
# Examples for the various modes follow.
# You must leave one of the following options enabled.
# Note that you must specify the same line for phase:1 and phase:2.
#
# Default: Anomaly Scoring mode, log to error log, log to ModSecurity audit log
# - By default, offending requests are blocked with an error 403 response.
# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
SecDefaultAction "phase:1,log,auditlog,pass"
SecDefaultAction "phase:2,log,auditlog,pass"
# Example: Anomaly Scoring mode, log only to ModSecurity audit log
# - By default, offending requests are blocked with an error 403 response.
# - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example
# and review section 'Changing the Disruptive Action for Anomaly Mode'.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
# SecDefaultAction "phase:1,nolog,auditlog,pass"
# SecDefaultAction "phase:2,nolog,auditlog,pass"
# Example: Self-contained mode, return error 403 on blocking
# - In this configuration the default disruptive action becomes 'deny'. After a
# rule triggers, it will stop processing the request and return an error 403.
# - You can also use a different error status, such as 404, 406, et cetera.
# - In Apache, you can use ErrorDocument to show a friendly error page or
# perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html
#
# SecDefaultAction "phase:1,log,auditlog,deny,status:403"
# SecDefaultAction "phase:2,log,auditlog,deny,status:403"
# Example: Self-contained mode, redirect back to homepage on blocking
# - In this configuration the 'tag' action includes the Host header data in the
# log. This helps to identify which virtual host triggered the rule (if any).
# - Note that this might cause redirect loops in some situations; for example
# if a Cookie or User-Agent header is blocked, it will also be blocked when
# the client subsequently tries to access the homepage. You can also redirect
# to another custom URL.
# SecDefaultAction "phase:1,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
# SecDefaultAction "phase:2,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'"
#
# -- [[ Paranoia Level Initialization ]] ---------------------------------------
#
# The Paranoia Level (PL) setting allows you to choose the desired level
# of rule checks that will add to your anomaly scores.
#
# With each paranoia level increase, the CRS enables additional rules
# giving you a higher level of security. However, higher paranoia levels
# also increase the possibility of blocking some legitimate traffic due to
# false alarms (also named false positives or FPs). If you use higher
# paranoia levels, it is likely that you will need to add some exclusion
# rules for certain requests and applications receiving complex input.
#
# - A paranoia level of 1 is default. In this level, most core rules
# are enabled. PL1 is advised for beginners, installations
# covering many different sites and applications, and for setups
# with standard security requirements.
# At PL1 you should face FPs rarely. If you encounter FPs, please
# open an issue on the CRS GitHub site and don't forget to attach your
# complete Audit Log record for the request with the issue.
# - Paranoia level 2 includes many extra rules, for instance enabling
# many regexp-based SQL and XSS injection protections, and adding
# extra keywords checked for code injections. PL2 is advised
# for moderate to experienced users desiring more complete coverage
# and for installations with elevated security requirements.
# PL2 comes with some FPs which you need to handle.
# - Paranoia level 3 enables more rules and keyword lists, and tweaks
# limits on special characters used. PL3 is aimed at users experienced
# at the handling of FPs and at installations with a high security
# requirement.
# - Paranoia level 4 further restricts special characters.
# The highest level is advised for experienced users protecting
# installations with very high security requirements. Running PL4 will
# likely produce a very high number of FPs which have to be
# treated before the site can go productive.
#
# All rules will log their PL to the audit log;
# example: [tag "paranoia-level/2"]. This allows you to deduct from the
# audit log how the WAF behavior is affected by paranoia level.
#
# It is important to also look into the variable
# tx.enforce_bodyproc_urlencoded (Enforce Body Processor URLENCODED)
# defined below. Enabling it closes a possible bypass of CRS.
#
# Uncomment this rule to change the default:
#
#SecAction \
# "id:900000,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.blocking_paranoia_level=1"
# It is possible to execute rules from a higher paranoia level but not include
# them in the anomaly scoring. This allows you to take a well-tuned system on
# paranoia level 1 and add rules from paranoia level 2 without having to fear
# the new rules would lead to false positives that raise your score above the
# threshold.
# This optional feature is enabled by uncommenting the following rule and
# setting the tx.detection_paranoia_level.
# Technically, rules up to the level defined in tx.detection_paranoia_level
# will be executed, but only the rules up to tx.blocking_paranoia_level affect the
# anomaly scores.
# By default, tx.detection_paranoia_level is set to tx.blocking_paranoia_level.
# tx.detection_paranoia_level must not be lower than tx.blocking_paranoia_level.
#
# Please notice that setting tx.detection_paranoia_level to a higher paranoia
# level results in a performance impact that is equally high as setting
# tx.blocking_paranoia_level to said level.
#
#SecAction \
# "id:900001,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.detection_paranoia_level=1"
#
# -- [[ Enforce Body Processor URLENCODED ]] -----------------------------------
#
# ModSecurity selects the body processor based on the Content-Type request
# header. But clients are not always setting the Content-Type header for their
# request body payloads. This will leave ModSecurity with limited vision into
# the payload. The variable tx.enforce_bodyproc_urlencoded lets you force the
# URLENCODED body processor in these situations. This is off by default, as it
# implies a change of the behaviour of ModSecurity beyond CRS (the body
# processor applies to all rules, not only CRS) and because it may lead to
# false positives already on paranoia level 1. However, enabling this variable
# closes a possible bypass of CRS so it should be considered.
#
# Uncomment this rule to change the default:
#
#SecAction \
# "id:900010,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.enforce_bodyproc_urlencoded=1"
#
# -- [[ Anomaly Scoring Mode Severity Levels ]] --------------------------------
#
# Each rule in the CRS has an associated severity level.
# These are the default scoring points for each severity level.
# These settings will be used to increment the anomaly score if a rule matches.
# You may adjust these points to your liking, but this is usually not needed.
#
# - CRITICAL severity: Anomaly Score of 5.
# Mostly generated by the application attack rules (93x and 94x files).
# - ERROR severity: Anomaly Score of 4.
# Generated mostly from outbound leakage rules (95x files).
# - WARNING severity: Anomaly Score of 3.
# Generated mostly by malicious client rules (91x files).
# - NOTICE severity: Anomaly Score of 2.
# Generated mostly by the protocol rules (92x files).
#
# In anomaly mode, these scores are cumulative.
# So it's possible for a request to hit multiple rules.
#
# (Note: In this file, we use 'phase:1' to set CRS configuration variables.
# In general, 'phase:request' is used. However, we want to make absolutely sure
# that all configuration variables are set before the CRS rules are processed.)
#
#SecAction \
# "id:900100,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.critical_anomaly_score=5,\
# setvar:tx.error_anomaly_score=4,\
# setvar:tx.warning_anomaly_score=3,\
# setvar:tx.notice_anomaly_score=2"
#
# -- [[ Anomaly Scoring Mode Blocking Threshold Levels ]] ----------------------
#
# Here, you can specify at which cumulative anomaly score an inbound request,
# or outbound response, gets blocked.
#
# Most detected inbound threats will give a critical score of 5.
# Smaller violations, like violations of protocol/standards, carry lower scores.
#
# [ At default value ]
# If you keep the blocking thresholds at the defaults, the CRS will work
# similarly to previous CRS versions: a single critical rule match will cause
# the request to be blocked and logged.
#
# [ Using higher values ]
# If you want to make the CRS less sensitive, you can increase the blocking
# thresholds, for instance to 7 (which would require multiple rule matches
# before blocking) or 10 (which would require at least two critical alerts - or
# a combination of many lesser alerts), or even higher. However, increasing the
# thresholds might cause some attacks to bypass the CRS rules or your policies.
#
# [ New deployment strategy: Starting high and decreasing ]
# It is a common practice to start a fresh CRS installation with elevated
# anomaly scoring thresholds (>100) and then lower the limits as your
# confidence in the setup grows. You may also look into the Sampling
# Percentage section below for a different strategy to ease into a new
# CRS installation.
#
# [ Anomaly Threshold / Paranoia Level Quadrant ]
#
# High Anomaly Limit | High Anomaly Limit
# Low Paranoia Level | High Paranoia Level
# -> Fresh Site | -> Experimental Site
# ------------------------------------------------------
# Low Anomaly Limit | Low Anomaly Limit
# Low Paranoia Level | High Paranoia Level
# -> Standard Site | -> High Security Site
#
# Uncomment this rule to change the defaults:
#
#SecAction \
# "id:900110,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.inbound_anomaly_score_threshold=5,\
# setvar:tx.outbound_anomaly_score_threshold=4"
#
# -- [[ Application Specific Rule Exclusions ]] --------------------------------
#
# CRS 3.x contained exclusion packages to tweak the CRS for use with common
# web applications, lowering the number of false positives.
#
# In CRS 4, these are no longer part of the CRS itself, but they are available
# as "CRS plugins". Some plugins improve support for web applications, and others
# may bring new functionality. Plugins are not installed by default, but can be
# downloaded from the plugin registry:
# https://github.com/coreruleset/plugin-registry
#
# For detailed information about using and installing plugins, please see:
# https://coreruleset.org/docs/concepts/plugins/
#
# -- [[ Anomaly Score Reporting Level ]] ---------------------------------------
#
# When a request is blocked due to the anomaly score meeting or exceeding the
# anomaly threshold then the blocking rule will also report the anomaly score.
# This applies to the separate inbound and outbound anomaly scores.
#
# In phase 5, there are additional rules that can perform additional reporting
# of anomaly scores with a verbosity that depends on the reporting level defined
# below.
#
# By setting the reporting level you control whether you want additional
# reporting beyond the blocking rule or not and, if yes, which requests should
# be covered. The higher the reporting level, the more verbose the reporting is.
#
# There are 6 reporting levels:
#
# 0 - Reporting disabled
# 1 - Reporting for requests with a blocking anomaly score >= a threshold
# 2 - Reporting for requests with a detection anomaly score >= a threshold
# 3 - Reporting for requests with a blocking anomaly score greater than 0
# 4 - Reporting for requests with a detection anomaly score greater than 0
# 5 - Reporting for all requests
#
# Note: Reporting levels 1 and 2 make it possible to differentiate between
# requests that are blocked and requests that are *not* blocked but would have
# been blocked if the blocking PL was equal to detection PL. This may be useful
# for certain FP tuning methodologies, for example moving to a higher PL.
#
# A value of 5 can be useful on platforms where you are interested in logging
# non-scoring requests, yet it is not possible to report this information in
# the request/access log. This applies to Nginx, for example.
#
#SecAction \
# "id:900115,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.reporting_level=4"
#
# -- [[ Early Anomaly Scoring Mode Blocking ]] ------------------------------
#
# The anomaly scores for the request and the responses are generally summed up
# and evaluated at the end of phase:2 and at the end of phase:4 respectively.
# However, it is possible to enable an early evaluation of these anomaly scores
# at the end of phase:1 and at the end of phase:3.
#
# If a request (or a response) hits the anomaly threshold in this early
# evaluation, then blocking happens immediately (if blocking is enabled) and
# the phase 2 (and phase 4 respectively) will no longer be executed.
#
# Enable the rule 900120 that sets the variable tx.early_blocking to 1 in order
# to enable early blocking. The variable tx.early_blocking is set to 0 by
# default. Early blocking is thus disabled by default.
#
# Please note that early blocking will hide potential alerts from you. This
# means that a payload that would appear in an alert in phase 2 (or phase 4)
# does not get evaluated if the request is being blocked early. So when you
# disabled early blocking again at some point in the future, then new alerts
# from phase 2 might pop up.
SecAction \
"id:900120,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.early_blocking=1"
#
# -- [[ HTTP Policy Settings ]] ------------------------------------------------
#
# This section defines your policies for the HTTP protocol, such as:
# - allowed HTTP versions, HTTP methods, allowed request Content-Types
# - forbidden file extensions (e.g. .bak, .sql) and request headers (e.g. Proxy)
#
# These variables are used in the following rule files:
# - REQUEST-911-METHOD-ENFORCEMENT.conf
# - REQUEST-920-PROTOCOL-ENFORCEMENT.conf
# HTTP methods that a client is allowed to use.
# Default: GET HEAD POST OPTIONS
# Example: for RESTful APIs, add the following methods: PUT PATCH DELETE
# Example: for WebDAV, add the following methods: CHECKOUT COPY DELETE LOCK
# MERGE MKACTIVITY MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK
# Uncomment this rule to change the default.
#SecAction \
# "id:900200,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.allowed_methods=GET HEAD POST OPTIONS'"
# Content-Types that a client is allowed to send in a request.
# Default: |application/x-www-form-urlencoded| |multipart/form-data| |multipart/related|
# |text/xml| |application/xml| |application/soap+xml| |application/json|
# |application/cloudevents+json| |application/cloudevents-batch+json|
#
# Please note, that the rule where CRS uses this variable (920420) evaluates it with operator
# `@within`, which is case sensitive, but uses t:lowercase. You must add your whole custom
# Content-Type with lowercase.
#
# Bypass Warning: some applications may not rely on the content-type request header in order
# to parse the request body. This could make an attacker able to send malicious URLENCODED/JSON/XML
# payloads without being detected by the WAF. Allowing request content-type that doesn't activate any
# body processor (for example: "text/plain", "application/x-amf", "application/octet-stream", etc..)
# could lead to a WAF bypass. For example, a malicious JSON payload submitted with a "text/plain"
# content type may still be interpreted as JSON by a backend application but would not trigger the
# JSON body parser at the WAF, leading to a bypass.
#
# To prevent blocking request with not allowed content-type by default, you can create an exclusion
# rule that removes rule 920420. For example:
#SecRule REQUEST_HEADERS:Content-Type "@rx ^text/plain" \
# "id:1234,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# ctl:ruleRemoveById=920420,\
# chain"
# SecRule REQUEST_URI "@rx ^/foo/bar" "t:none"
#
# Uncomment this rule to change the default.
#
#SecAction \
# "id:900220,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json|'"
# Allowed HTTP versions.
# Default: HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0
# Example for legacy clients: HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0
# Note that some web server versions use 'HTTP/2', some 'HTTP/2.0', so
# we include both version strings by default.
# Uncomment this rule to change the default.
#SecAction \
# "id:900230,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.allowed_http_versions=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0'"
# Forbidden file extensions.
# Guards against unintended exposure of development/configuration files.
# Default: .asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/
# Example: .bak/ .config/ .conf/ .db/ .ini/ .log/ .old/ .pass/ .pdb/ .rdb/ .sql/
# Note that .axd was removed due to false positives (see PR 1925).
#
# To additionally guard against configuration/install archive files from being
# accidentally exposed, common archive file extensions can be added to the
# restricted extensions list. An example list of common archive file extensions
# is presented below:
# .7z/ .br/ .bz/ .bz2/ .cab/ .cpio/ .gz/ .img/ .iso/ .jar/ .rar/ .tar/ .tbz2/ .tgz/ .txz/ .xz/ .zip/ .zst/
# (Source: https://en.wikipedia.org/wiki/List_of_archive_formats)
#
# Uncomment this rule to change the default.
#SecAction \
# "id:900240,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/'"
# Forbidden request headers.
# Header names should be lowercase, enclosed by /slashes/ as delimiters.
# Default: /accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/
#
# Note: Accept-Charset is a deprecated header that should not be used by clients and
# ignored by servers. It can be used for a response WAF bypass, by asking for a charset
# that the WAF cannot decode.
# Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset
#
# Note: Content-Encoding is used to list any encodings that have been applied to the
# original payload. It is only used for compression, which isn't supported by CRS by
# default since it blocks newlines and null bytes inside the request body. Most
# compression algorithms require at least null bytes per RFC. Blocking it shouldn't
# break anything and increases security since ModSecurity is incapable of properly
# scanning compressed request bodies.
#
# Note: Blocking Proxy header prevents 'httpoxy' vulnerability: https://httpoxy.org
#
# Note: Blocking the x-http-method-override,x-http-method and x-method-override headers
# prevents attacks as described here: https://www.sidechannel.blog/en/http-method-override-what-it-is-and-how-a-pentester-can-use-it
#
# Uncomment this rule to change the default.
#SecAction \
# "id:900250,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.restricted_headers=/accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/ /x-http-method-override/ /x-http-method/ /x-method-override/'"
# Content-Types charsets that a client is allowed to send in a request.
# The content-types are enclosed by |pipes| as delimiters to guarantee exact matches.
# Default: |utf-8| |iso-8859-1| |iso-8859-15| |windows-1252|
# Uncomment this rule to change the default.
#SecAction \
# "id:900280,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:'tx.allowed_request_content_type_charset=|utf-8| |iso-8859-1| |iso-8859-15| |windows-1252|'"
#
# -- [[ HTTP Argument/Upload Limits ]] -----------------------------------------
#
# Here you can define optional limits on HTTP get/post parameters and uploads.
# This can help to prevent application specific DoS attacks.
#
# These values are checked in REQUEST-920-PROTOCOL-ENFORCEMENT.conf.
# Beware of blocking legitimate traffic when enabling these limits.
#
# Block request if number of arguments is too high
# Default: unlimited
# Example: 255
# Uncomment this rule to set a limit.
#SecAction \
# "id:900300,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.max_num_args=255"
# Block request if the length of any argument name is too high
# Default: unlimited
# Example: 100
# Uncomment this rule to set a limit.
#SecAction \
# "id:900310,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.arg_name_length=100"
# Block request if the length of any argument value is too high
# Default: unlimited
# Example: 400
# Uncomment this rule to set a limit.
#SecAction \
# "id:900320,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.arg_length=400"
# Block request if the total length of all combined arguments is too high
# Default: unlimited
# Example: 64000
# Uncomment this rule to set a limit.
#SecAction \
# "id:900330,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.total_arg_length=64000"
# Block request if the file size of any individual uploaded file is too high
# Default: unlimited
# Example: 1048576
# Uncomment this rule to set a limit.
#SecAction \
# "id:900340,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.max_file_size=1048576"
# Block request if the total size of all combined uploaded files is too high
# Default: unlimited
# Example: 1048576
# Uncomment this rule to set a limit.
#SecAction \
# "id:900350,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.combined_file_sizes=1048576"
#
# -- [[ Easing In / Sampling Percentage ]] -------------------------------------
#
# Adding the Core Rule Set to an existing productive site can lead to false
# positives, unexpected performance issues and other undesired side effects.
#
# It can be beneficial to test the water first by enabling the CRS for a
# limited number of requests only and then, when you have solved the issues (if
# any) and you have confidence in the setup, to raise the ratio of requests
# being sent into the ruleset.
#
# Adjust the percentage of requests that are funnelled into the Core Rules by
# setting TX.sampling_percentage below. The default is 100, meaning that every
# request gets checked by the CRS. The selection of requests, which are going
# to be checked, is based on a pseudo random number generated by ModSecurity.
#
# If a request is allowed to pass without being checked by the CRS, there is no
# entry in the audit log (for performance reasons), but an error log entry is
# written. If you want to disable the error log entry, then issue the
# following directive somewhere after the inclusion of the CRS
# (E.g., RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf).
#
#SecRuleUpdateActionById 901450 "nolog"
#
# ATTENTION: If this TX.sampling_percentage is below 100, then some of the
# requests will bypass the Core Rules completely and you lose the ability to
# protect your service with ModSecurity.
#
# Uncomment this rule to enable this feature:
#
#SecAction \
# "id:900400,\
# phase:1,\
# pass,\
# nolog,\
# setvar:tx.sampling_percentage=100"
#
# -- [[ Check UTF-8 encoding ]] ------------------------------------------------
#
# The CRS can optionally check request contents for invalid UTF-8 encoding.
# We only want to apply this check if UTF-8 encoding is actually used by the
# site; otherwise it will result in false positives.
#
# Uncomment this rule to use this feature:
#
#SecAction \
# "id:900950,\
# phase:1,\
# pass,\
# t:none,\
# nolog,\
# setvar:tx.crs_validate_utf8_encoding=1"
#
# -- [[ Collection timeout ]] --------------------------------------------------
#
# Set the SecCollectionTimeout directive from the ModSecurity default (1 hour)
# to a lower setting which is appropriate to most sites.
# This increases performance by cleaning out stale collection (block) entries.
#
# This value should be greater than or equal to any block durations or timeouts
# set by plugins that make use of ModSecurity's persistent collections (e.g. the
# DoS protection and IP reputation plugins).
#
# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#SecCollectionTimeout
# Please keep this directive uncommented.
# Default: 600 (10 minutes)
SecCollectionTimeout 600
#
# -- [[ End of setup ]] --------------------------------------------------------
#
# The CRS checks the tx.crs_setup_version variable to ensure that the setup
# has been loaded. If you are not planning to use this setup template,
# you must manually set the tx.crs_setup_version variable before including
# the CRS rules/* files.
#
# The variable is a numerical representation of the CRS version number.
# E.g., v3.0.0 is represented as 300.
#
SecAction \
"id:900990,\
phase:1,\
pass,\
t:none,\
nolog,\
setvar:tx.crs_setup_version=400"

View File

@@ -0,0 +1,200 @@
# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.4.0.0-rc1
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2022 Core Rule Set project. All rights reserved.
#
# The OWASP ModSecurity Core Rule Set is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------
#
# The purpose of this file is to hold LOCAL exceptions for your site. The
# types of rules that would go into this file are one where you want to
# short-circuit inspection and allow certain transactions to pass through
# inspection or if you want to alter rules that are applied.
#
# This file is named REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example for a
# very specific reason. Files affixed with the .example extension are designed
# to contain user created/modified data. The '.example'. extension should be
# renamed to end in .conf. The advantage of this is that when OWASP CRS is
# updated, the updates will not overwrite a user generated configuration file.
#
# As a result of this design paradigm users are encouraged NOT to directly
# modify rules. Instead they should use this
# REQUEST-900-EXCLUSION-RULES-BEFORE-CRS and the
# RESPONSE-999-EXCLUSION-RULES-AFTER-CRS file to modify OWASP rules using
# methods similar to the examples specified below.
#
# REQUEST-900-EXCLUSION-RULES-BEFORE-CRS and
# RESPONSE-999-EXCLUSION-RULES-AFTER-CRS serve different purposes. ModSecurity
# effectively maintains two different context: startup, and per transaction.
# As a rule, directives are processed within the startup context. While they
# can affect the per transaction context they generally remain fixed during the
# execution of ModSecurity.
#
# As a result if one wanted to disable a rule at bootup the SecRuleRemoveById
# directive or one of its siblings would have to be placed AFTER the rule is
# listed, otherwise it will not have knowledge of the rules existence (since
# these rules are read in at the same time). This means that when using
# directives that effect SecRules, these exceptions should be placed AFTER all
# the existing rules. This is why RESPONSE-999-EXCLUSION-RULES-AFTER-CRS is
# designed such that it loads LAST.
#
# Conversely, ModSecurity supports several actions that can change the state of
# the underlying configuration during the per transaction context, this is when
# rules are being processed. Generally, these are accomplished by using the
# 'ctl' action. As these are part of a rule, they will be evaluated in the
# order rules are applied (by physical location, considering phases). As a
# result of this ordering a 'ctl' action should be placed with consideration to
# when it will be executed. This is particularly relevant for the 'ctl' options
# that involve modifying ID's (such as ruleRemoveById). In these cases it is
# important that such rules are placed BEFORE the rule ID they will affect.
# Unlike the setup context, by the time we process rules in the per-transaction
# context, we are already aware of all the rule ID's. It is by this logic that
# we include rules such as this BEFORE all the remaining rules. As a result
# REQUEST-900-EXCLUSION-RULES-BEFORE-CRS is designed to load FIRST.
#
# As a general rule:
# ctl:ruleEngine -> place in REQUEST-900-EXCLUSION-RULES-BEFORE-CRS
# ctl:ruleRemoveById -> place in REQUEST-900-EXCLUSION-RULES-BEFORE-CRS
# ctl:ruleRemoveByMsg -> place in REQUEST-900-EXCLUSION-RULES-BEFORE-CRS
# ctl:ruleRemoveByTag -> place in REQUEST-900-EXCLUSION-RULES-BEFORE-CRS
# ctl:ruleRemoveTargetById -> place in REQUEST-900-EXCLUSION-RULES-BEFORE-CRS
# ctl:ruleRemoveTargetByMsg -> place in REQUEST-900-EXCLUSION-RULES-BEFORE-CRS
# ctl:ruleRemoveTargetByTag -> place in REQUEST-900-EXCLUSION-RULES-BEFORE-CRS
#
# SecRuleRemoveById -> place in RESPONSE-999-EXCLUSION-RULES-AFTER-CRS
# SecRuleRemoveByMsg -> place in RESPONSE-999-EXCLUSION-RULES-AFTER-CRS
# SecRuleRemoveByTag -> place in RESPONSE-999-EXCLUSION-RULES-AFTER-CRS
# SecRuleUpdateActionById -> place in RESPONSE-999-EXCLUSION-RULES-AFTER-CRS
# SecRuleUpdateTargetById -> place in RESPONSE-999-EXCLUSION-RULES-AFTER-CRS
# SecRuleUpdateTargetByMsg -> place in RESPONSE-999-EXCLUSION-RULES-AFTER-CRS
# SecRuleUpdateTargetByTag -> place in RESPONSE-999-EXCLUSION-RULES-AFTER-CRS
#
#
# What follows are a group of examples that show you how to perform rule
# exclusions.
#
#
# Example Exclusion Rule: Disable inspection for an authorized client
#
# This ruleset allows you to control how ModSecurity will handle traffic
# originating from Authorized Vulnerability Scanning (AVS) sources. See
# related blog post -
# https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/updated-advanced-topic-of-the-week-handling-authorized-scanning-traffic/
#
# Allow List ASV network block (no blocking or logging of AVS traffic) Update
# IP network block as appropriate for your AVS traffic
#
# ModSec Rule Exclusion: Disable Rule Engine for known ASV IP
# SecRule REMOTE_ADDR "@ipMatch 192.168.1.100" \
# "id:1000,\
# phase:1,\
# pass,\
# nolog,\
# ctl:ruleEngine=Off"
#
#
# Example Exclusion Rule: Removing a specific ARGS parameter from inspection
# for an individual rule
#
# This rule shows how to conditionally exclude the "password"
# parameter for rule 942100 when the REQUEST_URI is /index.php
# ModSecurity Rule Exclusion: 942100 SQL Injection Detected via libinjection
#
# SecRule REQUEST_URI "@beginsWith /index.php" \
# "id:1001,\
# phase:1,\
# pass,\
# nolog,\
# ctl:ruleRemoveTargetById=942100;ARGS:password"
#
#
# Example Exclusion Rule: Removing a specific ARGS parameter from inspection
# for only certain attacks
#
# Attack rules within the CRS are tagged, with tags such as 'attack-lfi',
# 'attack-sqli', 'attack-xss', 'attack-injection-php', et cetera.
#
# ModSecurity Rule Exclusion: Disable inspection of ARGS:pwd
# for all rules tagged attack-sqli
# SecRule REQUEST_FILENAME "@endsWith /wp-login.php" \
# "id:1002,\
# phase:2,\
# pass,\
# nolog,\
# ctl:ruleRemoveTargetByTag=attack-sqli;ARGS:pwd"
#
# Example Exclusion Rule: Removing a specific ARGS parameter from inspection
# for all CRS rules
#
# This rule illustrates that we can use tagging very effectively to allow list a
# common false positive across an entire ModSecurity instance. This can be done
# because every rule in OWASP_CRS is tagged with OWASP_CRS. This will NOT
# affect custom rules.
#
# ModSecurity Rule Exclusion: Disable inspection of ARGS:pwd
# for all CRS rules
# SecRule REQUEST_FILENAME "@endsWith /wp-login.php" \
# "id:1003,\
# phase:2,\
# pass,\
# nolog,\
# ctl:ruleRemoveTargetByTag=OWASP_CRS;ARGS:pwd"
#
# Example Exclusion Rule: Removing a range of rules
#
# This rule illustrates that we can remove a rule range via a ctl action.
# This uses the fact, that rules are grouped by topic in rule files covering
# a certain id range.
#
# ModSecurity Rule Exclusion: Disable all SQLi and XSS rules
# SecRule REQUEST_FILENAME "@beginsWith /admin" \
# "id:1004,\
# phase:2,\
# pass,\
# nolog,\
# ctl:ruleRemoveById=941000-942999"
#
#
# The application-specific rule exclusion plugins
# (see: https://github.com/coreruleset/plugin-registry)
# provide additional examples which can be useful then tuning a service.
#
# Example Rule: Allow monitoring tools and scripts
#
# Uncomment this rule to allow all requests from trusted IPs and User-Agent.
# This can be useful for monitoring tools like Monit, Nagios, or other agents.
# For example, if you're using AWS Load Balancer, you may need to trust all
# requests from "10.0.0.0/8" subnet that come with the user-agent
# "ELB-HealthChecker/2.0". By doing this, all requests that match these
# conditions will not be matched against the following rules:
#
# - id: 911100 (allowed methods)
# - id: 913100,913110,913120,913101,913102 (scan detection)
# - id: 920280 (missing/empty host header)
# - id: 920350 (IP address in host header)
# - tag: attack-disclosure (all RESPONSE-*-DATA-LEAKAGES rules)
#
# SecRule REMOTE_ADDR "@ipMatch 10.0.0.0/8" \
# "id:1005,\
# phase:1,\
# pass,\
# nolog,\
# chain"
# SecRule REQUEST_METHOD "@pm GET HEAD" "chain"
# SecRule REQUEST_HEADERS:User-Agent "@pm ELB-HealthChecker" \
# "ctl:ruleRemoveById=911100,\
# ctl:ruleRemoveById=913100,\
# ctl:ruleRemoveById=913110,\
# ctl:ruleRemoveById=913120,\
# ctl:ruleRemoveById=913101,\
# ctl:ruleRemoveById=913102,\
# ctl:ruleRemoveById=920280,\
# ctl:ruleRemoveById=920350,\
# ctl:ruleRemoveByTag=attack-disclosure"

View File

@@ -0,0 +1,424 @@
# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.4.0.0-rc1
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2022 Core Rule Set project. All rights reserved.
#
# The OWASP ModSecurity Core Rule Set is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------
#
# This file REQUEST-901-INITIALIZATION.conf initializes the Core Rules
# and performs preparatory actions. It also fixes errors and omissions
# of variable definitions in the file crs-setup.conf.
# The crs-setup.conf can and should be edited by the user, this file
# is part of the CRS installation and should not be altered.
#
#
# -=[ Rules Version ]=-
#
# Rule version data is added to the "Producer" line of Section H of the Audit log:
#
# - Producer: ModSecurity for Apache/2.9.1 (http://www.modsecurity.org/); OWASP_CRS/3.1.0.
#
# Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual#wiki-SecComponentSignature
#
SecComponentSignature "OWASP_CRS/4.0.0-rc1"
#
# -=[ Default setup values ]=-
#
# The CRS checks the tx.crs_setup_version variable to ensure that the setup
# file is included at the correct time. This detects situations where
# necessary settings are not defined, for instance if the file
# inclusion order is incorrect, or if the user has forgotten to
# include the crs-setup.conf file.
#
# If you are upgrading from an earlier version of the CRS and you are
# getting this error, please make a new copy of the setup template
# crs-setup.conf.example to crs-setup.conf, and re-apply your policy
# changes. There have been many changes in settings syntax from CRS2
# to CRS3, so an old setup file may cause unwanted behavior.
#
# If you are not planning to use the crs-setup.conf template, you must
# manually set the tx.crs_setup_version variable before including
# the CRS rules/* files.
#
# The variable is a numerical representation of the CRS version number.
# E.g., v3.0.0 is represented as 300.
#
SecRule &TX:crs_setup_version "@eq 0" \
"id:901001,\
phase:1,\
deny,\
status:500,\
log,\
auditlog,\
msg:'ModSecurity Core Rule Set is deployed without configuration! Please copy the crs-setup.conf.example template to crs-setup.conf, and include the crs-setup.conf file in your webserver configuration before including the CRS rules. See the INSTALL file in the CRS directory for detailed instructions',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL'"
#
# -=[ Default setup values ]=-
#
# Some constructs or individual rules will fail if certain parameters
# are not set in the crs-setup.conf file. The following rules will catch
# these cases and assign sane default values.
#
# Default Inbound Anomaly Threshold Level (rule 900110 in crs-setup.conf)
SecRule &TX:inbound_anomaly_score_threshold "@eq 0" \
"id:901100,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.inbound_anomaly_score_threshold=5'"
# Default Outbound Anomaly Threshold Level (rule 900110 in crs-setup.conf)
SecRule &TX:outbound_anomaly_score_threshold "@eq 0" \
"id:901110,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.outbound_anomaly_score_threshold=4'"
# Default Reporting Level (rule 900115 in crs-setup.conf)
SecRule &TX:reporting_level "@eq 0" \
"id:901111,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.reporting_level=4'"
# Default Early Blocking (rule 900120 in crs-setup.conf)
SecRule &TX:early_blocking "@eq 0" \
"id:901115,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.early_blocking=0'"
# Default Blocking Paranoia Level (rule 900000 in crs-setup.conf)
SecRule &TX:blocking_paranoia_level "@eq 0" \
"id:901120,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.blocking_paranoia_level=1'"
# Default Detection Paranoia Level (rule 900001 in crs-setup.conf)
SecRule &TX:detection_paranoia_level "@eq 0" \
"id:901125,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.detection_paranoia_level=%{TX.blocking_paranoia_level}'"
# Default Sampling Percentage (rule 900400 in crs-setup.conf)
SecRule &TX:sampling_percentage "@eq 0" \
"id:901130,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.sampling_percentage=100'"
# Default Anomaly Scores (rule 900100 in crs-setup.conf)
SecRule &TX:critical_anomaly_score "@eq 0" \
"id:901140,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.critical_anomaly_score=5'"
SecRule &TX:error_anomaly_score "@eq 0" \
"id:901141,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.error_anomaly_score=4'"
SecRule &TX:warning_anomaly_score "@eq 0" \
"id:901142,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.warning_anomaly_score=3'"
SecRule &TX:notice_anomaly_score "@eq 0" \
"id:901143,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.notice_anomaly_score=2'"
# Default HTTP policy: allowed_methods (rule 900200 in crs-setup.conf)
SecRule &TX:allowed_methods "@eq 0" \
"id:901160,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.allowed_methods=GET HEAD POST OPTIONS'"
# Default HTTP policy: allowed_request_content_type (rule 900220 in crs-setup.conf)
SecRule &TX:allowed_request_content_type "@eq 0" \
"id:901162,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json|'"
# Default HTTP policy: allowed_request_content_type_charset (rule 900280 in crs-setup.conf)
SecRule &TX:allowed_request_content_type_charset "@eq 0" \
"id:901168,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.allowed_request_content_type_charset=|utf-8| |iso-8859-1| |iso-8859-15| |windows-1252|'"
# Default HTTP policy: allowed_http_versions (rule 900230 in crs-setup.conf)
SecRule &TX:allowed_http_versions "@eq 0" \
"id:901163,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.allowed_http_versions=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0'"
# Default HTTP policy: restricted_extensions (rule 900240 in crs-setup.conf)
SecRule &TX:restricted_extensions "@eq 0" \
"id:901164,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/'"
# Default HTTP policy: restricted_headers (rule 900250 in crs-setup.conf)
SecRule &TX:restricted_headers "@eq 0" \
"id:901165,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.restricted_headers=/accept-charset/ /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/ /x-http-method-override/ /x-http-method/ /x-method-override/'"
# Default enforcing of body processor URLENCODED (rule 900010 in crs-setup.conf)
SecRule &TX:enforce_bodyproc_urlencoded "@eq 0" \
"id:901167,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.enforce_bodyproc_urlencoded=0'"
# Default check for UTF8 encoding validation (rule 900950 in crs-setup.conf)
SecRule &TX:crs_validate_utf8_encoding "@eq 0" \
"id:901169,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.crs_validate_utf8_encoding=0'"
#
# -=[ Initialize internal variables ]=-
#
# Initialize anomaly scoring variables.
# All _score variables start at 0, and are incremented by the various rules
# upon detection of a possible attack.
SecAction \
"id:901200,\
phase:1,\
pass,\
t:none,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.blocking_inbound_anomaly_score=0',\
setvar:'tx.detection_inbound_anomaly_score=0',\
setvar:'tx.inbound_anomaly_score_pl1=0',\
setvar:'tx.inbound_anomaly_score_pl2=0',\
setvar:'tx.inbound_anomaly_score_pl3=0',\
setvar:'tx.inbound_anomaly_score_pl4=0',\
setvar:'tx.sql_injection_score=0',\
setvar:'tx.xss_score=0',\
setvar:'tx.rfi_score=0',\
setvar:'tx.lfi_score=0',\
setvar:'tx.rce_score=0',\
setvar:'tx.php_injection_score=0',\
setvar:'tx.http_violation_score=0',\
setvar:'tx.session_fixation_score=0',\
setvar:'tx.blocking_outbound_anomaly_score=0',\
setvar:'tx.detection_outbound_anomaly_score=0',\
setvar:'tx.outbound_anomaly_score_pl1=0',\
setvar:'tx.outbound_anomaly_score_pl2=0',\
setvar:'tx.outbound_anomaly_score_pl3=0',\
setvar:'tx.outbound_anomaly_score_pl4=0',\
setvar:'tx.anomaly_score=0'"
#
# -=[ Initialize collections ]=-
#
# Create both Global and IP collections for rules to use.
# There are some CRS rules that assume that these two collections
# have already been initiated.
#
SecRule REQUEST_HEADERS:User-Agent "@rx ^.*$" \
"id:901318,\
phase:1,\
pass,\
t:none,t:sha1,t:hexEncode,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'tx.ua_hash=%{MATCHED_VAR}'"
SecAction \
"id:901321,\
phase:1,\
pass,\
t:none,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
initcol:global=global,\
initcol:ip=%{remote_addr}_%{tx.ua_hash}"
#
# -=[ Initialize Correct Body Processing ]=-
#
# Force request body variable and optionally request body processor
#
# Force body variable
SecRule REQBODY_PROCESSOR "!@rx (?:URLENCODED|MULTIPART|XML|JSON)" \
"id:901340,\
phase:1,\
pass,\
nolog,\
noauditlog,\
msg:'Enabling body inspection',\
ctl:forceRequestBodyVariable=On,\
ver:'OWASP_CRS/4.0.0-rc1'"
# Force body processor URLENCODED
SecRule TX:enforce_bodyproc_urlencoded "@eq 1" \
"id:901350,\
phase:1,\
pass,\
t:none,t:urlDecodeUni,\
nolog,\
noauditlog,\
msg:'Enabling forced body inspection for ASCII content',\
ver:'OWASP_CRS/4.0.0-rc1',\
chain"
SecRule REQBODY_PROCESSOR "!@rx (?:URLENCODED|MULTIPART|XML|JSON)" \
"ctl:requestBodyProcessor=URLENCODED"
#
# -=[ Easing In / Sampling Percentage ]=-
#
# This is used to send only a limited percentage of requests into the Core
# Rule Set. The selection is based on TX.sampling_percentage and a pseudo
# random number calculated below.
#
# Use this to ease into a new Core Rules installation with an existing
# productive service.
#
# See
# https://www.netnea.com/cms/2016/04/26/easing-in-conditional-modsecurity-rule-execution-based-on-pseudo-random-numbers/
#
#
# Generate the pseudo random number
#
# ATTENTION: This is no cryptographically secure random number. It's just
# a cheap way to get some random number suitable for sampling.
#
# We take the entropy contained in the UNIQUE_ID. We hash that variable and
# take the first integer numbers out of it. Theoretically, it is possible
# but highly improbable that there are no integers in a hexEncoded sha1 hash.
# In the very rare event that two integers are not matched (due to only being
# a-f in all, or all but one positions) 901450 will not be triggered.
# Leading zeros are not removed from the two-digit random number, and are
# handled gracefullly by 901450
SecRule TX:sampling_percentage "@eq 100" \
"id:901400,\
phase:1,\
pass,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
skipAfter:END-SAMPLING"
SecRule UNIQUE_ID "@rx ^[a-f]*([0-9])[a-f]*([0-9])" \
"id:901410,\
phase:1,\
pass,\
capture,\
t:sha1,t:hexEncode,\
nolog,\
ver:'OWASP_CRS/4.0.0-rc1',\
setvar:'TX.sampling_rnd100=%{TX.1}%{TX.2}'"
#
# Sampling decision
#
# If a request is allowed to pass without being checked by the CRS, there is no
# entry in the audit log (for performance reasons), but an error log entry is
# being written. If you want to disable the error log entry, then issue the
# following directive somewhere after the inclusion of the CRS
# (E.g., RESPONSE-999-EXCEPTIONS.conf).
#
# SecRuleUpdateActionById 901450 "nolog"
#
SecRule TX:sampling_rnd100 "!@lt %{tx.sampling_percentage}" \
"id:901450,\
phase:1,\
pass,\
log,\
noauditlog,\
msg:'Sampling: Disable the rule engine based on sampling_percentage %{TX.sampling_percentage} and random number %{TX.sampling_rnd100}',\
ctl:ruleRemoveByTag=OWASP_CRS,\
ver:'OWASP_CRS/4.0.0-rc1'"
SecMarker "END-SAMPLING"
#
# Configuration Plausibility Checks
#
# Make sure detection paranoia level is not lower than paranoia level
SecRule TX:detection_paranoia_level "@lt %{tx.blocking_paranoia_level}" \
"id:901500,\
phase:1,\
deny,\
status:500,\
t:none,\
log,\
msg:'Detection paranoia level configured is lower than the paranoia level itself. This is illegal. Blocking request. Aborting',\
ver:'OWASP_CRS/4.0.0-rc1'"

View File

@@ -0,0 +1,55 @@
# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.4.0.0-rc1
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2022 Core Rule Set project. All rights reserved.
#
# The OWASP ModSecurity Core Rule Set is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------
# This file is used as an exception mechanism to remove common false positives
# that may be encountered.
#
# Exception for Apache SSL pinger
#
SecRule REQUEST_LINE "@streq GET /" \
"id:905100,\
phase:1,\
pass,\
t:none,\
nolog,\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-apache',\
tag:'attack-generic',\
ver:'OWASP_CRS/4.0.0-rc1',\
chain"
SecRule REMOTE_ADDR "@ipMatch 127.0.0.1,::1" \
"t:none,\
ctl:ruleRemoveByTag=OWASP_CRS,\
ctl:auditEngine=Off"
#
# Exception for Apache internal dummy connection
#
SecRule REMOTE_ADDR "@ipMatch 127.0.0.1,::1" \
"id:905110,\
phase:1,\
pass,\
t:none,\
nolog,\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-apache',\
tag:'attack-generic',\
ver:'OWASP_CRS/4.0.0-rc1',\
chain"
SecRule REQUEST_HEADERS:User-Agent "@endsWith (internal dummy connection)" \
"t:none,\
chain"
SecRule REQUEST_LINE "@rx ^(?:GET /|OPTIONS \*) HTTP/[12]\.[01]$" \
"t:none,\
ctl:ruleRemoveByTag=OWASP_CRS,\
ctl:auditEngine=Off"

View File

@@ -0,0 +1,76 @@
# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.4.0.0-rc1
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2022 Core Rule Set project. All rights reserved.
#
# The OWASP ModSecurity Core Rule Set is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------
#
# -= Paranoia Level 0 (empty) =- (apply unconditionally)
#
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 1" "id:911011,phase:1,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 1" "id:911012,phase:2,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
#
# -= Paranoia Level 1 (default) =- (apply only when tx.detection_paranoia_level is sufficiently high: 1 or higher)
#
#
# -=[ Allowed Request Methods ]=-
#
# tx.allowed_methods is defined in the crs-setup.conf file
#
SecRule REQUEST_METHOD "!@within %{tx.allowed_methods}" \
"id:911100,\
phase:1,\
block,\
msg:'Method is not allowed by policy',\
logdata:'%{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-generic',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/210/272/220/274',\
tag:'PCI/12.1',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 2" "id:911013,phase:1,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 2" "id:911014,phase:2,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
#
# -= Paranoia Level 2 =- (apply only when tx.detection_paranoia_level is sufficiently high: 2 or higher)
#
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 3" "id:911015,phase:1,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 3" "id:911016,phase:2,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
#
# -= Paranoia Level 3 =- (apply only when tx.detection_paranoia_level is sufficiently high: 3 or higher)
#
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 4" "id:911017,phase:1,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 4" "id:911018,phase:2,pass,nolog,skipAfter:END-REQUEST-911-METHOD-ENFORCEMENT"
#
# -= Paranoia Level 4 =- (apply only when tx.detection_paranoia_level is sufficiently high: 4 or higher)
#
#
# -= Paranoia Levels Finished =-
#
SecMarker "END-REQUEST-911-METHOD-ENFORCEMENT"

View File

@@ -0,0 +1,189 @@
# ------------------------------------------------------------------------
# OWASP ModSecurity Core Rule Set ver.4.0.0-rc1
# Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved.
# Copyright (c) 2021-2022 Core Rule Set project. All rights reserved.
#
# The OWASP ModSecurity Core Rule Set is distributed under
# Apache Software License (ASL) version 2
# Please see the enclosed LICENSE file for full details.
# ------------------------------------------------------------------------
#
# -= Paranoia Level 0 (empty) =- (apply unconditionally)
#
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 1" "id:913011,phase:1,pass,nolog,skipAfter:END-REQUEST-913-SCANNER-DETECTION"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 1" "id:913012,phase:2,pass,nolog,skipAfter:END-REQUEST-913-SCANNER-DETECTION"
#
# -= Paranoia Level 1 (default) =- (apply only when tx.detection_paranoia_level is sufficiently high: 1 or higher)
#
#
# -=[ Vulnerability Scanner Checks ]=-
#
# These rules inspect the default User-Agent and Header values sent by
# various commercial and open source vuln scanners.
#
# The following rules contain User-Agent lists:
# 913100 - security scanners (data file scanners-user-agents.data)
# 913101 - scripting/generic HTTP clients (data file scripting-user-agents.data)
# 913102 - web crawlers/bots (data file crawlers-user-agents.data)
#
# Chained rule is allow listing:
# YUM package manager of CentOS / Fedore: User-Agent: urlgrabber/3.10 yum/3.4.3
# eCairn service: User-Agent: mozilla/5.0 ecairn-grabber/1.0 (+http://ecairn.com/grabber)
SecRule REQUEST_HEADERS:User-Agent "@pmFromFile scanners-user-agents.data" \
"id:913100,\
phase:1,\
block,\
capture,\
t:none,\
msg:'Found User-Agent associated with security scanner',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-reputation-scanner',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/118/224/541/310',\
tag:'PCI/6.5.10',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
chain"
SecRule MATCHED_VARS "!@rx ^(?:urlgrabber/[0-9\.]+ yum/[0-9\.]+|mozilla/[0-9\.]+ ecairn-grabber/[0-9\.]+ \(\+http://ecairn.com/grabber\))$" \
"setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
SecRule REQUEST_HEADERS_NAMES|REQUEST_HEADERS "@pmFromFile scanners-headers.data" \
"id:913110,\
phase:1,\
block,\
capture,\
t:none,\
msg:'Found request header associated with security scanner',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-reputation-scanner',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/118/224/541/310',\
tag:'PCI/6.5.10',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
SecRule REQUEST_FILENAME|ARGS "@pmFromFile scanners-urls.data" \
"id:913120,\
phase:2,\
block,\
capture,\
t:none,\
msg:'Found request filename/argument associated with security scanner',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-reputation-scanner',\
tag:'paranoia-level/1',\
tag:'OWASP_CRS',\
tag:'capec/1000/118/224/541/310',\
tag:'PCI/6.5.10',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 2" "id:913013,phase:1,pass,nolog,skipAfter:END-REQUEST-913-SCANNER-DETECTION"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 2" "id:913014,phase:2,pass,nolog,skipAfter:END-REQUEST-913-SCANNER-DETECTION"
#
# -= Paranoia Level 2 =- (apply only when tx.detection_paranoia_level is sufficiently high: 2 or higher)
#
#
# -=[ Scripting/Generic User-Agents ]=-
#
# This rule detects user-agents associated with various HTTP client libraries
# and scripting languages. Detection suggests attempted access by some
# automated tool.
#
# This rule is a sibling of rule 913100.
#
SecRule REQUEST_HEADERS:User-Agent "@pmFromFile scripting-user-agents.data" \
"id:913101,\
phase:1,\
block,\
capture,\
t:none,\
msg:'Found User-Agent associated with scripting/generic HTTP client',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-reputation-scripting',\
tag:'OWASP_CRS',\
tag:'capec/1000/118/224/541/310',\
tag:'PCI/6.5.10',\
tag:'paranoia-level/2',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.inbound_anomaly_score_pl2=+%{tx.critical_anomaly_score}'"
#
# -=[ Crawler User-Agents ]=-
#
# This rule detects user-agents associated with various crawlers, SEO tools,
# and bots, which have been reported to potentially misbehave.
# These crawlers can have legitimate uses when used with authorization.
#
# This rule is a sibling of rule 913100.
#
SecRule REQUEST_HEADERS:User-Agent "@pmFromFile crawlers-user-agents.data" \
"id:913102,\
phase:1,\
block,\
capture,\
t:none,\
msg:'Found User-Agent associated with web crawler/bot',\
logdata:'Matched Data: %{TX.0} found within %{MATCHED_VAR_NAME}: %{MATCHED_VAR}',\
tag:'application-multi',\
tag:'language-multi',\
tag:'platform-multi',\
tag:'attack-reputation-crawler',\
tag:'OWASP_CRS',\
tag:'capec/1000/118/116/150',\
tag:'PCI/6.5.10',\
tag:'paranoia-level/2',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.inbound_anomaly_score_pl2=+%{tx.critical_anomaly_score}'"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 3" "id:913015,phase:1,pass,nolog,skipAfter:END-REQUEST-913-SCANNER-DETECTION"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 3" "id:913016,phase:2,pass,nolog,skipAfter:END-REQUEST-913-SCANNER-DETECTION"
#
# -= Paranoia Level 3 =- (apply only when tx.detection_paranoia_level is sufficiently high: 3 or higher)
#
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 4" "id:913017,phase:1,pass,nolog,skipAfter:END-REQUEST-913-SCANNER-DETECTION"
SecRule TX:DETECTION_PARANOIA_LEVEL "@lt 4" "id:913018,phase:2,pass,nolog,skipAfter:END-REQUEST-913-SCANNER-DETECTION"
#
# -= Paranoia Level 4 =- (apply only when tx.detection_paranoia_level is sufficiently high: 4 or higher)
#
#
# -= Paranoia Levels Finished =-
#
SecMarker "END-REQUEST-913-SCANNER-DETECTION"

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