mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 21:21:01 +08:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c76ae26bb | ||
|
|
c1250aec2e | ||
|
|
02bc319eef | ||
|
|
28892cf3ae | ||
|
|
bee03a37a4 | ||
|
|
2a97921d2b | ||
|
|
d4dbaba760 | ||
|
|
d718870b65 | ||
|
|
b65446fa25 | ||
|
|
3fd37abab7 | ||
|
|
81e467b624 | ||
|
|
736eea6cf9 | ||
|
|
c32e1ab69b | ||
|
|
fc05a3b256 | ||
|
|
9fc2760b7d | ||
|
|
be88647752 | ||
|
|
a56172095a | ||
|
|
f3270123ba | ||
|
|
5e2d62406b | ||
|
|
39cab9d724 | ||
|
|
89865733f6 | ||
|
|
1ccf9195b2 | ||
|
|
7d2a05ef1c | ||
|
|
32c2acefda | ||
|
|
ea7b581e26 | ||
|
|
ac2f0a5545 | ||
|
|
51d7124454 | ||
|
|
ec6a185adc | ||
|
|
f9ffda288b | ||
|
|
2dbe41324a | ||
|
|
80d6ecfddb | ||
|
|
b23fae7a12 | ||
|
|
2c19d97252 | ||
|
|
efd7ccd5fe | ||
|
|
81fd0d6386 | ||
|
|
176ddc6963 | ||
|
|
44637c2449 |
120
.github/workflows/build-and-test.yaml
vendored
120
.github/workflows/build-and-test.yaml
vendored
@@ -23,6 +23,28 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Golang Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
external
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
# test
|
||||
- name: Run Coverage Tests
|
||||
run: GOPROXY="https://proxy.golang.org,direct" make go.test.coverage
|
||||
@@ -38,15 +60,37 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint,coverage-test]
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }}"
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Setup Go"
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
- name: "checkout ${{ github.ref }}"
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Golang Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
path: |-
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-go
|
||||
|
||||
- name: Setup Submodule Caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |-
|
||||
envoy
|
||||
istio
|
||||
external
|
||||
.git/modules
|
||||
key: ${{ runner.os }}-submodules-${{ github.run_id }}
|
||||
restore-keys: ${{ runner.os }}-submodules
|
||||
|
||||
- run: git stash # restore patch
|
||||
|
||||
- name: "Build Higress Binary"
|
||||
run: GOPROXY="https://proxy.golang.org,direct" make build
|
||||
@@ -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
|
||||
|
||||
144
.github/workflows/build-image-and-push.yaml
vendored
Normal file
144
.github/workflows/build-image-and-push.yaml
vendored
Normal 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
|
||||
37
.github/workflows/deploy-standalone-to-oss.yaml
vendored
Normal file
37
.github/workflows/deploy-standalone-to-oss.yaml
vendored
Normal 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
|
||||
5
.github/workflows/deploy-to-oss.yaml
vendored
5
.github/workflows/deploy-to-oss.yaml
vendored
@@ -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
4
.gitignore
vendored
@@ -12,4 +12,6 @@ bazel-out
|
||||
bazel-testlogs
|
||||
bazel-wasm-cpp
|
||||
tools/bin/
|
||||
helm/**/charts/**.tgz
|
||||
helm/**/charts/**.tgz
|
||||
target/
|
||||
tools/hack/cluster.conf
|
||||
@@ -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
|
||||
|
||||
@@ -125,7 +125,7 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
|
||||
|
||||
社区交流群:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
开发者群:
|
||||
|
||||
@@ -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
|
||||
|
||||
1666
api/networking/v1/http_2_rpc.pb.go
Normal file
1666
api/networking/v1/http_2_rpc.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
74
api/networking/v1/http_2_rpc.proto
Normal file
74
api/networking/v1/http_2_rpc.proto
Normal 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 {
|
||||
}
|
||||
121
api/networking/v1/http_2_rpc_deepcopy.gen.go
Normal file
121
api/networking/v1/http_2_rpc_deepcopy.gen.go
Normal 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()
|
||||
}
|
||||
78
api/networking/v1/http_2_rpc_json.gen.go
Normal file
78
api/networking/v1/http_2_rpc_json.gen.go
Normal 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}
|
||||
)
|
||||
@@ -41,6 +41,8 @@ func Resource(resource string) schema.GroupResource {
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Http2Rpc{},
|
||||
&Http2RpcList{},
|
||||
&McpBridge{},
|
||||
&McpBridgeList{},
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -16,4 +16,6 @@
|
||||
|
||||
package v1
|
||||
|
||||
type Http2RpcExpansion interface{}
|
||||
|
||||
type McpBridgeExpansion interface{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
92
client/pkg/listers/networking/v1/http2rpc.gen.go
Normal file
92
client/pkg/listers/networking/v1/http2rpc.gen.go
Normal 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
1
helm/core/.helmignore
Normal file
@@ -0,0 +1 @@
|
||||
crds/customresourcedefinitions.gen_lt1.16.yaml
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
176
helm/core/crds/customresourcedefinitions.gen_lt1.16.yaml
Normal file
176
helm/core/crds/customresourcedefinitions.gen_lt1.16.yaml
Normal file
@@ -0,0 +1,176 @@
|
||||
# DO NOT EDIT - Generated by Cue OpenAPI generator based on Istio APIs.
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
name: wasmplugins.extensions.higress.io
|
||||
spec:
|
||||
group: extensions.higress.io
|
||||
names:
|
||||
categories:
|
||||
- higress-io
|
||||
- extensions-higress-io
|
||||
kind: WasmPlugin
|
||||
listKind: WasmPluginList
|
||||
plural: wasmplugins
|
||||
singular: wasmplugin
|
||||
scope: Namespaced
|
||||
additionalPrinterColumns:
|
||||
- description: 'CreationTimestamp is a timestamp representing the server time
|
||||
when this object was created. It is not guaranteed to be set in happens-before
|
||||
order across separate operations. Clients may not set this value. It is represented
|
||||
in RFC3339 form and is in UTC. Populated by the system. Read-only. Null for
|
||||
lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata'
|
||||
JSONPath: .metadata.creationTimestamp
|
||||
name: Age
|
||||
type: date
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
served: true
|
||||
storage: true
|
||||
version: v1alpha1
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
spec:
|
||||
properties:
|
||||
defaultConfig:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
defaultConfigDisable:
|
||||
type: boolean
|
||||
imagePullPolicy:
|
||||
description: The pull behaviour to be applied when fetching an OCI
|
||||
image.
|
||||
enum:
|
||||
- UNSPECIFIED_POLICY
|
||||
- IfNotPresent
|
||||
- Always
|
||||
type: string
|
||||
imagePullSecret:
|
||||
description: Credentials to use for OCI image pulling.
|
||||
type: string
|
||||
matchRules:
|
||||
items:
|
||||
properties:
|
||||
config:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
configDisable:
|
||||
type: boolean
|
||||
domain:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
ingress:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
phase:
|
||||
description: Determines where in the filter chain this `WasmPlugin`
|
||||
is to be injected.
|
||||
enum:
|
||||
- UNSPECIFIED_PHASE
|
||||
- AUTHN
|
||||
- AUTHZ
|
||||
- STATS
|
||||
type: string
|
||||
pluginConfig:
|
||||
description: The configuration that will be passed on to the plugin.
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
pluginName:
|
||||
type: string
|
||||
priority:
|
||||
description: Determines ordering of `WasmPlugins` in the same `phase`.
|
||||
nullable: true
|
||||
type: integer
|
||||
sha256:
|
||||
description: SHA256 checksum that will be used to verify Wasm module
|
||||
or OCI container.
|
||||
type: string
|
||||
url:
|
||||
description: URL of a Wasm module or OCI container.
|
||||
type: string
|
||||
verificationKey:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
name: mcpbridges.networking.higress.io
|
||||
spec:
|
||||
group: networking.higress.io
|
||||
names:
|
||||
categories:
|
||||
- higress-io
|
||||
kind: McpBridge
|
||||
listKind: McpBridgeList
|
||||
plural: mcpbridges
|
||||
singular: mcpbridge
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1
|
||||
served: true
|
||||
storage: true
|
||||
version: v1
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
properties:
|
||||
spec:
|
||||
properties:
|
||||
registries:
|
||||
items:
|
||||
properties:
|
||||
consulNamespace:
|
||||
type: string
|
||||
domain:
|
||||
type: string
|
||||
nacosAccessKey:
|
||||
type: string
|
||||
nacosAddressServer:
|
||||
type: string
|
||||
nacosGroups:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
nacosNamespace:
|
||||
type: string
|
||||
nacosNamespaceId:
|
||||
type: string
|
||||
nacosRefreshInterval:
|
||||
format: int64
|
||||
type: integer
|
||||
nacosSecretKey:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
port:
|
||||
type: integer
|
||||
type:
|
||||
type: string
|
||||
zkServicesPath:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
@@ -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"]
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" \
|
||||
|
||||
30
istio/1.12/patches/istio/20230618-debug-api-anonymous.patch
Normal file
30
istio/1.12/patches/istio/20230618-debug-api-anonymous.patch
Normal 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 {
|
||||
15
istio/1.12/patches/istio/20230627-debug-fix-configz.patch
Normal file
15
istio/1.12/patches/istio/20230627-debug-fix-configz.patch
Normal 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)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{},
|
||||
|
||||
53
pkg/ingress/kube/annotations/http2rpc.go
Normal file
53
pkg/ingress/kube/annotations/http2rpc.go
Normal 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)
|
||||
}
|
||||
59
pkg/ingress/kube/annotations/http2rpc_test.go
Normal file
59
pkg/ingress/kube/annotations/http2rpc_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
rewritePath = "rewrite-path"
|
||||
rewriteTarget = "rewrite-target"
|
||||
useRegex = "use-regex"
|
||||
fullPathRegex = "full-path-regex"
|
||||
upstreamVhost = "upstream-vhost"
|
||||
|
||||
re2Regex = "\\$[0-9]"
|
||||
@@ -38,6 +39,7 @@ var (
|
||||
type RewriteConfig struct {
|
||||
RewriteTarget string
|
||||
UseRegex bool
|
||||
FullPathRegex bool
|
||||
RewriteHost string
|
||||
RewritePath string
|
||||
}
|
||||
@@ -52,13 +54,16 @@ func (r rewrite) Parse(annotations Annotations, config *Ingress, _ *GlobalContex
|
||||
rewriteConfig := &RewriteConfig{}
|
||||
rewriteConfig.RewriteTarget, _ = annotations.ParseStringASAP(rewriteTarget)
|
||||
rewriteConfig.UseRegex, _ = annotations.ParseBoolASAP(useRegex)
|
||||
rewriteConfig.FullPathRegex, _ = annotations.ParseBoolForHigress(fullPathRegex)
|
||||
rewriteConfig.RewriteHost, _ = annotations.ParseStringASAP(upstreamVhost)
|
||||
rewriteConfig.RewritePath, _ = annotations.ParseStringForHigress(rewritePath)
|
||||
|
||||
if rewriteConfig.RewritePath == "" && rewriteConfig.RewriteTarget != "" {
|
||||
// When rewrite target is present and not empty,
|
||||
// we will enforce regex match on all rules in this ingress.
|
||||
rewriteConfig.UseRegex = true
|
||||
if !rewriteConfig.UseRegex && !rewriteConfig.FullPathRegex {
|
||||
rewriteConfig.UseRegex = true
|
||||
}
|
||||
|
||||
// We should convert nginx regex rule to envoy regex rule.
|
||||
rewriteConfig.RewriteTarget = convertToRE2(rewriteConfig.RewriteTarget)
|
||||
@@ -108,12 +113,13 @@ func convertToRE2(target string) string {
|
||||
|
||||
func NeedRegexMatch(annotations map[string]string) bool {
|
||||
target, _ := Annotations(annotations).ParseStringASAP(rewriteTarget)
|
||||
regex, _ := Annotations(annotations).ParseBoolASAP(useRegex)
|
||||
useRegex, _ := Annotations(annotations).ParseBoolASAP(useRegex)
|
||||
fullPathRegex, _ := Annotations(annotations).ParseBoolForHigress(fullPathRegex)
|
||||
|
||||
return regex || target != ""
|
||||
return useRegex || target != "" || fullPathRegex
|
||||
}
|
||||
|
||||
func needRewriteConfig(annotations Annotations) bool {
|
||||
return annotations.HasASAP(rewriteTarget) || annotations.HasASAP(useRegex) ||
|
||||
annotations.HasASAP(upstreamVhost) || annotations.HasHigress(rewritePath)
|
||||
annotations.HasASAP(upstreamVhost) || annotations.HasHigress(rewritePath) || annotations.HasHigress(fullPathRegex)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,11 @@ const (
|
||||
|
||||
Prefix PathType = "prefix"
|
||||
|
||||
Regex PathType = "regex"
|
||||
// PrefixRegex :if PathType is PrefixRegex, then the /foo/bar/[A-Z0-9]{3} is actually ^/foo/bar/[A-Z0-9]{3}.*
|
||||
PrefixRegex PathType = "prefixRegex"
|
||||
|
||||
// FullPathRegex :if PathType is FullPathRegex, then the /foo/bar/[A-Z0-9]{3} is actually ^/foo/bar/[A-Z0-9]{3}$
|
||||
FullPathRegex PathType = "fullPathRegex"
|
||||
|
||||
DefaultStatusUpdateInterval = 10 * time.Second
|
||||
|
||||
|
||||
@@ -43,11 +43,11 @@ func TestConstructRouteName(t *testing.T) {
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
Host: "*.test.com",
|
||||
OriginPathType: Regex,
|
||||
OriginPathType: PrefixRegex,
|
||||
OriginPath: "/test/(.*)/?[0-9]",
|
||||
HTTPRoute: &networking.HTTPRoute{},
|
||||
},
|
||||
expect: "*.test.com-regex-/test/(.*)/?[0-9]",
|
||||
expect: "*.test.com-prefixRegex-/test/(.*)/?[0-9]",
|
||||
},
|
||||
{
|
||||
input: &WrapperHTTPRoute{
|
||||
@@ -390,7 +390,7 @@ func TestSortRoutes(t *testing.T) {
|
||||
AnnotationsConfig: &annotations.Ingress{},
|
||||
},
|
||||
Host: "test.com",
|
||||
OriginPathType: Regex,
|
||||
OriginPathType: PrefixRegex,
|
||||
OriginPath: "/d(.*)",
|
||||
ClusterId: "cluster1",
|
||||
HTTPRoute: &networking.HTTPRoute{
|
||||
|
||||
50
pkg/ingress/kube/configmap/config.go
Normal file
50
pkg/ingress/kube/configmap/config.go
Normal 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)
|
||||
}
|
||||
202
pkg/ingress/kube/configmap/controller.go
Normal file
202
pkg/ingress/kube/configmap/controller.go
Normal 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
|
||||
}
|
||||
408
pkg/ingress/kube/configmap/tracing.go
Normal file
408
pkg/ingress/kube/configmap/tracing.go
Normal file
@@ -0,0 +1,408 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package 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 skywalking,zipkin 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
|
||||
}
|
||||
36
pkg/ingress/kube/http2rpc/controller.go
Normal file
36
pkg/ingress/kube/http2rpc/controller.go
Normal 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)
|
||||
}
|
||||
@@ -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},
|
||||
|
||||
@@ -39,8 +39,7 @@ type statusSyncer struct {
|
||||
|
||||
watchedNamespace string
|
||||
|
||||
ingressLister ingresslister.IngressLister
|
||||
ingressClassLister ingresslister.IngressClassLister
|
||||
ingressLister ingresslister.IngressLister
|
||||
// search service in the mse vpc
|
||||
serviceLister listerv1.ServiceLister
|
||||
}
|
||||
@@ -48,11 +47,10 @@ type statusSyncer struct {
|
||||
// newStatusSyncer creates a new instance
|
||||
func newStatusSyncer(localKubeClient, client kubelib.Client, controller *controller, namespace string) *statusSyncer {
|
||||
return &statusSyncer{
|
||||
client: client,
|
||||
controller: controller,
|
||||
watchedNamespace: namespace,
|
||||
ingressLister: client.KubeInformer().Networking().V1beta1().Ingresses().Lister(),
|
||||
ingressClassLister: client.KubeInformer().Networking().V1beta1().IngressClasses().Lister(),
|
||||
client: client,
|
||||
controller: controller,
|
||||
watchedNamespace: namespace,
|
||||
ingressLister: client.KubeInformer().Networking().V1beta1().Ingresses().Lister(),
|
||||
// search service in the mse vpc
|
||||
serviceLister: localKubeClient.KubeInformer().Core().V1().Services().Lister(),
|
||||
}
|
||||
|
||||
@@ -517,8 +517,12 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
||||
|
||||
var pathType common.PathType
|
||||
originPath := httpPath.Path
|
||||
if wrapper.AnnotationsConfig.NeedRegexMatch() {
|
||||
pathType = common.Regex
|
||||
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
|
||||
if annotationsConfig.IsPrefixRegexMatch() {
|
||||
pathType = common.PrefixRegex
|
||||
} else if annotationsConfig.IsFullPathRegexMatch() {
|
||||
pathType = common.FullPathRegex
|
||||
}
|
||||
} else {
|
||||
switch *httpPath.PathType {
|
||||
case ingress.PathTypeExact:
|
||||
@@ -610,10 +614,14 @@ func (c *controller) generateHttpMatches(pathType common.PathType, path string,
|
||||
|
||||
httpMatch := &networking.HTTPMatchRequest{}
|
||||
switch pathType {
|
||||
case common.Regex:
|
||||
case common.PrefixRegex:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Regex{Regex: path + ".*"},
|
||||
}
|
||||
case common.FullPathRegex:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Regex{Regex: path + "$"},
|
||||
}
|
||||
case common.Exact:
|
||||
httpMatch.Uri = &networking.StringMatch{
|
||||
MatchType: &networking.StringMatch_Exact{Exact: path},
|
||||
@@ -747,8 +755,12 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
|
||||
|
||||
var pathType common.PathType
|
||||
originPath := httpPath.Path
|
||||
if wrapper.AnnotationsConfig.NeedRegexMatch() {
|
||||
pathType = common.Regex
|
||||
if annotationsConfig := wrapper.AnnotationsConfig; annotationsConfig.NeedRegexMatch() {
|
||||
if annotationsConfig.IsPrefixRegexMatch() {
|
||||
pathType = common.PrefixRegex
|
||||
} else if annotationsConfig.IsFullPathRegexMatch() {
|
||||
pathType = common.FullPathRegex
|
||||
}
|
||||
} else {
|
||||
switch *httpPath.PathType {
|
||||
case ingress.PathTypeExact:
|
||||
|
||||
@@ -40,8 +40,7 @@ type statusSyncer struct {
|
||||
|
||||
watchedNamespace string
|
||||
|
||||
ingressLister ingresslister.IngressLister
|
||||
ingressClassLister ingresslister.IngressClassLister
|
||||
ingressLister ingresslister.IngressLister
|
||||
// search service in the mse vpc
|
||||
serviceLister listerv1.ServiceLister
|
||||
}
|
||||
@@ -49,11 +48,10 @@ type statusSyncer struct {
|
||||
// newStatusSyncer creates a new instance
|
||||
func newStatusSyncer(localKubeClient, client kubelib.Client, controller *controller, namespace string) *statusSyncer {
|
||||
return &statusSyncer{
|
||||
client: client,
|
||||
controller: controller,
|
||||
watchedNamespace: namespace,
|
||||
ingressLister: client.KubeInformer().Networking().V1().Ingresses().Lister(),
|
||||
ingressClassLister: client.KubeInformer().Networking().V1().IngressClasses().Lister(),
|
||||
client: client,
|
||||
controller: controller,
|
||||
watchedNamespace: namespace,
|
||||
ingressLister: client.KubeInformer().Networking().V1().Ingresses().Lister(),
|
||||
// search service in the mse vpc
|
||||
serviceLister: localKubeClient.KubeInformer().Core().V1().Services().Lister(),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
21
plugins/wasm-go/extensions/chatgpt-proxy/README.md
Normal file
21
plugins/wasm-go/extensions/chatgpt-proxy/README.md
Normal 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"
|
||||
```
|
||||
|
||||
|
||||
15
plugins/wasm-go/extensions/chatgpt-proxy/go.mod
Normal file
15
plugins/wasm-go/extensions/chatgpt-proxy/go.mod
Normal 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
|
||||
)
|
||||
22
plugins/wasm-go/extensions/chatgpt-proxy/go.sum
Normal file
22
plugins/wasm-go/extensions/chatgpt-proxy/go.sum
Normal 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=
|
||||
128
plugins/wasm-go/extensions/chatgpt-proxy/main.go
Normal file
128
plugins/wasm-go/extensions/chatgpt-proxy/main.go
Normal 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
|
||||
}
|
||||
BIN
plugins/wasm-go/extensions/chatgpt-proxy/main.wasm
Normal file
BIN
plugins/wasm-go/extensions/chatgpt-proxy/main.wasm
Normal file
Binary file not shown.
230
plugins/wasm-go/extensions/cors/README.md
Normal file
230
plugins/wasm-go/extensions/cors/README.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# 功能说明
|
||||
|
||||
`cors` 插件可以为服务端启用 CORS(Cross-Origin Resource Sharing,跨域资源共享)的返回 http 响应头。
|
||||
|
||||
# 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
|-----------------------|-----------------|-------|---------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| allow_origins | array of string | 选填 | * | 允许跨域访问的 Origin,格式为 scheme://host:port,示例如 http://example.com:8081。当 allow_credentials 为 false 时,可以使用 * 来表示允许所有 Origin 通过 |
|
||||
| allow_origin_patterns | array of string | 选填 | - | 允许跨域访问的 Origin 模式匹配, 用 * 匹配域名或者端口, <br/>比如 http://*.example.com -- 匹配域名, http://*.example.com:[8080,9090] -- 匹配域名和指定端口, http://*.example.com:[*] -- 匹配域名和所有端口。单独 * 表示匹配所有域名和端口 |
|
||||
| allow_methods | array of string | 选填 | GET, PUT, POST, DELETE, PATCH, OPTIONS | 允许跨域访问的 Method,比如:GET,POST 等。可以使用 * 来表示允许所有 Method。 |
|
||||
| allow_headers | array of string | 选填 | DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,<br/>If-Modified-Since,Cache-Control,Content-Type,Authorization | 允许跨域访问时请求方携带哪些非 CORS 规范以外的 Header。可以使用 * 来表示允许任意 Header。 |
|
||||
| expose_headers | array of string | 选填 | - | 允许跨域访问时响应方携带哪些非 CORS 规范以外的 Header。可以使用 * 来表示允许任意 Header。 |
|
||||
| allow_credentials | bool | 选填 | false | 是否允许跨域访问的请求方携带凭据(如 Cookie 等)。根据 CORS 规范,如果设置该选项为 true,在 allow_origins 不能使用 *, 替换成使用 allow_origin_patterns * |
|
||||
| max_age | number | 选填 | 86400秒 | 浏览器缓存 CORS 结果的最大时间,单位为秒。<br/>在这个时间范围内,浏览器会复用上一次的检查结果 |
|
||||
|
||||
> 注意
|
||||
> * allow_credentials 是一个很敏感的选项,请谨慎开启。开启之后,allow_credentials 和 allow_origins 为 * 不能同时使用,同时设置时, allow_origins 值为 "*" 生效。
|
||||
> * allow_origins 和 allow_origin_patterns 可以同时设置, 先检查 allow_origins 是否匹配,然后再检查 allow_origin_patterns 是否匹配
|
||||
> * 非法 CORS 请求, HTTP 状态码返回是 403, 返回体内容为 "Invalid CORS request"
|
||||
|
||||
# 配置示例
|
||||
|
||||
## 允许所有跨域访问, 不允许请求方携带凭据
|
||||
```yaml
|
||||
allow_origins:
|
||||
- '*'
|
||||
allow_methods:
|
||||
- '*'
|
||||
allow_headers:
|
||||
- '*'
|
||||
expose_headers:
|
||||
- '*'
|
||||
allow_credentials: false
|
||||
max_age: 7200
|
||||
```
|
||||
|
||||
## 允许所有跨域访问,同时允许请求方携带凭据
|
||||
```yaml
|
||||
allow_origin_patterns:
|
||||
- '*'
|
||||
allow_methods:
|
||||
- '*'
|
||||
allow_headers:
|
||||
- '*'
|
||||
expose_headers:
|
||||
- '*'
|
||||
allow_credentials: true
|
||||
max_age: 7200
|
||||
```
|
||||
|
||||
## 允许特定子域,特定方法,特定请求头跨域访问,同时允许请求方携带凭据
|
||||
```yaml
|
||||
allow_origin_patterns:
|
||||
- http://*.example.com
|
||||
- http://*.example.org:[8080,9090]
|
||||
allow_methods:
|
||||
- GET
|
||||
- PUT
|
||||
- POST
|
||||
- DELETE
|
||||
allow_headers:
|
||||
- Token
|
||||
- Content-Type
|
||||
- Authorization
|
||||
expose_headers:
|
||||
- '*'
|
||||
allow_credentials: true
|
||||
max_age: 7200
|
||||
```
|
||||
|
||||
# 测试
|
||||
|
||||
## 测试配置
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.higress.io/v1
|
||||
kind: McpBridge
|
||||
metadata:
|
||||
name: mcp-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
registries:
|
||||
- domain: httpbin.org
|
||||
name: httpbin
|
||||
port: 80
|
||||
type: dns
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/destination: httpbin.dns
|
||||
higress.io/upstream-vhost: "httpbin.org"
|
||||
higress.io/backend-protocol: HTTP
|
||||
name: ingress-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: httpbin.example.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
resource:
|
||||
apiGroup: networking.higress.io
|
||||
kind: McpBridge
|
||||
name: mcp-cors-httpbin
|
||||
path: /
|
||||
pathType: Prefix
|
||||
---
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: wasm-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
defaultConfigDisable: true
|
||||
matchRules:
|
||||
- config:
|
||||
allow_origins:
|
||||
- http://httpbin.example.net
|
||||
allow_origin_patterns:
|
||||
- http://*.example.com:[*]
|
||||
- http://*.example.org:[9090,8080]
|
||||
allow_methods:
|
||||
- GET
|
||||
- POST
|
||||
- PATCH
|
||||
allow_headers:
|
||||
- Content-Type
|
||||
- Token
|
||||
- Authorization
|
||||
expose_headers:
|
||||
- X-Custom-Header
|
||||
- X-Env-UTM
|
||||
allow_credentials: true
|
||||
max_age: 3600
|
||||
configDisable: false
|
||||
ingress:
|
||||
- ingress-cors-httpbin
|
||||
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/cors:1.0.0
|
||||
imagePullPolicy: Always
|
||||
```
|
||||
|
||||
## 请求测试
|
||||
|
||||
### 简单请求
|
||||
```shell
|
||||
curl -v -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
< HTTP/1.1 200 OK
|
||||
> x-cors-version: 1.0.0
|
||||
> access-control-allow-origin: http://httpbin2.example.org:9090
|
||||
> access-control-expose-headers: X-Custom-Header,X-Env-UTM
|
||||
> access-control-allow-credentials: true
|
||||
```
|
||||
|
||||
### 预检请求
|
||||
|
||||
```shell
|
||||
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: Content-Type, Token" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
< HTTP/1.1 200 OK
|
||||
< x-cors-version: 1.0.0
|
||||
< access-control-allow-origin: http://httpbin2.example.org:9090
|
||||
< access-control-allow-methods: GET,POST,PATCH
|
||||
< access-control-allow-headers: Content-Type,Token,Authorization
|
||||
< access-control-expose-headers: X-Custom-Header,X-Env-UTM
|
||||
< access-control-allow-credentials: true
|
||||
< access-control-max-age: 3600
|
||||
< date: Tue, 23 May 2023 11:41:28 GMT
|
||||
< server: istio-envoy
|
||||
< content-length: 0
|
||||
<
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
* Closing connection 0
|
||||
```
|
||||
|
||||
### 非法 CORS Origin 预检请求
|
||||
|
||||
```shell
|
||||
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: GET" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
HTTP/1.1 403 Forbidden
|
||||
< content-length: 70
|
||||
< content-type: text/plain
|
||||
< x-cors-version: 1.0.0
|
||||
< date: Tue, 23 May 2023 11:27:01 GMT
|
||||
< server: istio-envoy
|
||||
<
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
Invalid CORS request
|
||||
```
|
||||
|
||||
### 非法 CORS Method 预检请求
|
||||
|
||||
```shell
|
||||
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: DELETE" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
< HTTP/1.1 403 Forbidden
|
||||
< content-length: 49
|
||||
< content-type: text/plain
|
||||
< x-cors-version: 1.0.0
|
||||
< date: Tue, 23 May 2023 11:28:51 GMT
|
||||
< server: istio-envoy
|
||||
<
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
Invalid CORS request
|
||||
```
|
||||
|
||||
### 非法 CORS Header 预检请求
|
||||
|
||||
```shell
|
||||
curl -v -X OPTIONS -H "Origin: http://httpbin2.example.org:9090" -H "Host: httpbin.example.com" -H "Access-Control-Request-Method: GET" -H "Access-Control-Request-Headers: TokenView" http://127.0.0.1/anything/get\?foo\=1
|
||||
|
||||
< HTTP/1.1 403 Forbidden
|
||||
< content-length: 52
|
||||
< content-type: text/plain
|
||||
< x-cors-version: 1.0.0
|
||||
< date: Tue, 23 May 2023 11:31:03 GMT
|
||||
< server: istio-envoy
|
||||
<
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
Invalid CORS request
|
||||
```
|
||||
|
||||
# 参考文档
|
||||
- https://www.ruanyifeng.com/blog/2016/04/cors.html
|
||||
- https://fetch.spec.whatwg.org/#http-cors-protocol
|
||||
1
plugins/wasm-go/extensions/cors/VERSION
Normal file
1
plugins/wasm-go/extensions/cors/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
457
plugins/wasm-go/extensions/cors/config/cors_config.go
Normal file
457
plugins/wasm-go/extensions/cors/config/cors_config.go
Normal file
@@ -0,0 +1,457 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMatchAll = "*"
|
||||
defaultAllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS"
|
||||
defaultAllAllowMethods = "GET, PUT, POST, DELETE, PATCH, OPTIONS, HEAD, TRACE, CONNECT"
|
||||
defaultAllowHeaders = "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With," +
|
||||
"If-Modified-Since,Cache-Control,Content-Type,Authorization"
|
||||
defaultMaxAge = 86400
|
||||
protocolHttpName = "http"
|
||||
protocolHttpPort = "80"
|
||||
protocolHttpsName = "https"
|
||||
protocolHttpsPort = "443"
|
||||
|
||||
HeaderPluginDebug = "X-Cors-Version"
|
||||
HeaderPluginTrace = "X-Cors-Trace"
|
||||
HeaderOrigin = "Origin"
|
||||
HttpMethodOptions = "OPTIONS"
|
||||
|
||||
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
||||
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
|
||||
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
||||
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
||||
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
||||
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
|
||||
|
||||
HeaderControlRequestMethod = "Access-Control-Request-Method"
|
||||
HeaderControlRequestHeaders = "Access-Control-Request-Headers"
|
||||
|
||||
HttpContextKey = "CORS"
|
||||
)
|
||||
|
||||
var portsRegex = regexp.MustCompile(`(.*):\[(\*|\d+(,\d+)*)]`)
|
||||
|
||||
type OriginPattern struct {
|
||||
declaredPattern string
|
||||
pattern *regexp.Regexp
|
||||
patternValue string
|
||||
}
|
||||
|
||||
func newOriginPatternFromString(declaredPattern string) OriginPattern {
|
||||
declaredPattern = strings.ToLower(strings.TrimSuffix(declaredPattern, "/"))
|
||||
matches := portsRegex.FindAllStringSubmatch(declaredPattern, -1)
|
||||
portList := ""
|
||||
patternValue := declaredPattern
|
||||
if len(matches) > 0 {
|
||||
patternValue = matches[0][1]
|
||||
portList = matches[0][2]
|
||||
}
|
||||
|
||||
patternValue = "\\Q" + patternValue + "\\E"
|
||||
patternValue = strings.ReplaceAll(patternValue, "*", "\\E.*\\Q")
|
||||
if len(portList) > 0 {
|
||||
if portList == defaultMatchAll {
|
||||
patternValue += "(:\\d+)?"
|
||||
} else {
|
||||
patternValue += ":(" + strings.ReplaceAll(portList, ",", "|") + ")"
|
||||
}
|
||||
}
|
||||
|
||||
return OriginPattern{
|
||||
declaredPattern: declaredPattern,
|
||||
patternValue: patternValue,
|
||||
pattern: regexp.MustCompile(patternValue),
|
||||
}
|
||||
}
|
||||
|
||||
type CorsConfig struct {
|
||||
// allowOrigins A list of origins for which cross-origin requests are allowed.
|
||||
// Be a specific domain, e.g. "https://example.com", or the CORS defined special value "*" for all origins.
|
||||
// Keep in mind however that the CORS spec does not allow "*" when allowCredentials is set to true, using allowOriginPatterns instead
|
||||
// By default, it is set to "*" when allowOriginPatterns is not set too.
|
||||
allowOrigins []string
|
||||
|
||||
// allowOriginPatterns A list of origin patterns for which cross-origin requests are allowed
|
||||
// origins patterns with "*" anywhere in the host name in addition to port
|
||||
// lists Examples:
|
||||
// https://*.example.com -- domains ending with example.com
|
||||
// https://*.example.com:[8080,9090] -- domains ending with example.com on port 8080 or port 9090
|
||||
// https://*.example.com:[*] -- domains ending with example.com on any port, including the default port
|
||||
// The special value "*" allows all origins
|
||||
// By default, it is not set.
|
||||
allowOriginPatterns []OriginPattern
|
||||
|
||||
// allowMethods A list of method for which cross-origin requests are allowed
|
||||
// The special value "*" allows all methods.
|
||||
// By default, it is set to "GET, PUT, POST, DELETE, PATCH, OPTIONS".
|
||||
allowMethods []string
|
||||
|
||||
// allowHeaders A list of headers that a pre-flight request can list as allowed
|
||||
// The special value "*" allows actual requests to send any header
|
||||
// By default, it is set to "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
|
||||
allowHeaders []string
|
||||
|
||||
// exposeHeaders A list of response headers an actual response might have and can be exposed.
|
||||
// The special value "*" allows all headers to be exposed for non-credentialed requests.
|
||||
// By default, it is not set
|
||||
exposeHeaders []string
|
||||
|
||||
// allowCredentials Whether user credentials are supported.
|
||||
// By default, it is not set (i.e. user credentials are not supported).
|
||||
allowCredentials bool
|
||||
|
||||
// maxAge Configure how long, in seconds, the response from a pre-flight request can be cached by clients.
|
||||
// By default, it is set to 86400 seconds.
|
||||
maxAge int
|
||||
}
|
||||
|
||||
type HttpCorsContext struct {
|
||||
IsValid bool
|
||||
ValidReason string
|
||||
IsPreFlight bool
|
||||
IsCorsRequest bool
|
||||
AllowOrigin string
|
||||
AllowMethods string
|
||||
AllowHeaders string
|
||||
ExposeHeaders string
|
||||
AllowCredentials bool
|
||||
MaxAge int
|
||||
}
|
||||
|
||||
func (c *CorsConfig) GetVersion() string {
|
||||
return "1.0.0"
|
||||
}
|
||||
|
||||
func (c *CorsConfig) FillDefaultValues() {
|
||||
if len(c.allowOrigins) == 0 && len(c.allowOriginPatterns) == 0 && c.allowCredentials == false {
|
||||
c.allowOrigins = []string{defaultMatchAll}
|
||||
}
|
||||
if len(c.allowHeaders) == 0 {
|
||||
c.allowHeaders = []string{defaultAllowHeaders}
|
||||
}
|
||||
if len(c.allowMethods) == 0 {
|
||||
c.allowMethods = strings.Split(defaultAllowMethods, ",")
|
||||
}
|
||||
if c.maxAge == 0 {
|
||||
c.maxAge = defaultMaxAge
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddAllowOrigin(origin string) error {
|
||||
origin = strings.TrimSpace(origin)
|
||||
if len(origin) == 0 {
|
||||
return nil
|
||||
}
|
||||
if origin == defaultMatchAll {
|
||||
if c.allowCredentials == true {
|
||||
return errors.New("can't set origin to * when allowCredentials is true, use AllowOriginPatterns instead")
|
||||
}
|
||||
c.allowOrigins = []string{defaultMatchAll}
|
||||
return nil
|
||||
}
|
||||
c.allowOrigins = append(c.allowOrigins, strings.TrimSuffix(origin, "/"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddAllowHeader(header string) {
|
||||
header = strings.TrimSpace(header)
|
||||
if len(header) == 0 {
|
||||
return
|
||||
}
|
||||
if header == defaultMatchAll {
|
||||
c.allowHeaders = []string{defaultMatchAll}
|
||||
return
|
||||
}
|
||||
c.allowHeaders = append(c.allowHeaders, header)
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddAllowMethod(method string) {
|
||||
method = strings.TrimSpace(method)
|
||||
if len(method) == 0 {
|
||||
return
|
||||
}
|
||||
if method == defaultMatchAll {
|
||||
c.allowMethods = []string{defaultMatchAll}
|
||||
return
|
||||
}
|
||||
c.allowMethods = append(c.allowMethods, strings.ToUpper(method))
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddExposeHeader(header string) {
|
||||
header = strings.TrimSpace(header)
|
||||
if len(header) == 0 {
|
||||
return
|
||||
}
|
||||
if header == defaultMatchAll {
|
||||
c.exposeHeaders = []string{defaultMatchAll}
|
||||
return
|
||||
}
|
||||
c.exposeHeaders = append(c.exposeHeaders, header)
|
||||
}
|
||||
|
||||
func (c *CorsConfig) AddAllowOriginPattern(pattern string) {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if len(pattern) == 0 {
|
||||
return
|
||||
}
|
||||
originPattern := newOriginPatternFromString(pattern)
|
||||
c.allowOriginPatterns = append(c.allowOriginPatterns, originPattern)
|
||||
}
|
||||
|
||||
func (c *CorsConfig) SetAllowCredentials(allowCredentials bool) error {
|
||||
if allowCredentials && len(c.allowOrigins) > 0 && c.allowOrigins[0] == defaultMatchAll {
|
||||
return errors.New("can't set allowCredentials to true when allowOrigin is *")
|
||||
}
|
||||
c.allowCredentials = allowCredentials
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CorsConfig) SetMaxAge(maxAge int) {
|
||||
if maxAge <= 0 {
|
||||
c.maxAge = defaultMaxAge
|
||||
} else {
|
||||
c.maxAge = maxAge
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CorsConfig) Process(scheme string, host string, method string, headers [][2]string) (HttpCorsContext, error) {
|
||||
scheme = strings.ToLower(strings.TrimSpace(scheme))
|
||||
host = strings.ToLower(strings.TrimSpace(host))
|
||||
method = strings.ToLower(strings.TrimSpace(method))
|
||||
|
||||
// Init httpCorsContext with default values
|
||||
httpCorsContext := HttpCorsContext{IsValid: true, IsPreFlight: false, IsCorsRequest: false, AllowCredentials: false, MaxAge: 0}
|
||||
|
||||
// Get request origin, controlRequestMethod, controlRequestHeaders from http headers
|
||||
origin := ""
|
||||
controlRequestMethod := ""
|
||||
controlRequestHeaders := ""
|
||||
for _, header := range headers {
|
||||
key := header[0]
|
||||
// Get origin
|
||||
if strings.ToLower(key) == strings.ToLower(HeaderOrigin) {
|
||||
origin = strings.TrimSuffix(strings.TrimSpace(header[1]), "/")
|
||||
}
|
||||
// Get control request method & headers
|
||||
if strings.ToLower(key) == strings.ToLower(HeaderControlRequestMethod) {
|
||||
controlRequestMethod = strings.TrimSpace(header[1])
|
||||
}
|
||||
if strings.ToLower(key) == strings.ToLower(HeaderControlRequestHeaders) {
|
||||
controlRequestHeaders = strings.TrimSpace(header[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse if request is CORS and pre-flight request.
|
||||
isCorsRequest := c.isCorsRequest(scheme, host, origin)
|
||||
isPreFlight := c.isPreFlight(origin, method, controlRequestMethod)
|
||||
httpCorsContext.IsCorsRequest = isCorsRequest
|
||||
httpCorsContext.IsPreFlight = isPreFlight
|
||||
|
||||
// Skip when it is not CORS request
|
||||
if !isCorsRequest {
|
||||
httpCorsContext.IsValid = true
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
// Check origin
|
||||
allowOrigin, originOk := c.checkOrigin(origin)
|
||||
if !originOk {
|
||||
// Reject: origin is not allowed
|
||||
httpCorsContext.IsValid = false
|
||||
httpCorsContext.ValidReason = fmt.Sprintf("origin:%s is not allowed", origin)
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
// Check method
|
||||
requestMethod := method
|
||||
if isPreFlight {
|
||||
requestMethod = controlRequestMethod
|
||||
}
|
||||
allowMethods, methodOk := c.checkMethods(requestMethod)
|
||||
if !methodOk {
|
||||
// Reject: method is not allowed
|
||||
httpCorsContext.IsValid = false
|
||||
httpCorsContext.ValidReason = fmt.Sprintf("method:%s is not allowed", requestMethod)
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
// Check headers
|
||||
allowHeaders, headerOK := c.checkHeaders(controlRequestHeaders)
|
||||
|
||||
if isPreFlight && !headerOK {
|
||||
// Reject: headers are not allowed
|
||||
httpCorsContext.IsValid = false
|
||||
httpCorsContext.ValidReason = "Reject: headers are not allowed"
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
// Store result in httpCorsContext and return it.
|
||||
httpCorsContext.AllowOrigin = allowOrigin
|
||||
if isPreFlight {
|
||||
httpCorsContext.AllowMethods = allowMethods
|
||||
}
|
||||
if isPreFlight && len(allowHeaders) > 0 {
|
||||
httpCorsContext.AllowHeaders = allowHeaders
|
||||
}
|
||||
if isPreFlight && c.maxAge > 0 {
|
||||
httpCorsContext.MaxAge = c.maxAge
|
||||
}
|
||||
if len(c.exposeHeaders) > 0 {
|
||||
httpCorsContext.ExposeHeaders = strings.Join(c.exposeHeaders, ",")
|
||||
}
|
||||
httpCorsContext.AllowCredentials = c.allowCredentials
|
||||
|
||||
return httpCorsContext, nil
|
||||
}
|
||||
|
||||
func (c *CorsConfig) checkOrigin(origin string) (string, bool) {
|
||||
origin = strings.TrimSpace(origin)
|
||||
if len(origin) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
matchOrigin := strings.ToLower(origin)
|
||||
// Check exact match
|
||||
for _, allowOrigin := range c.allowOrigins {
|
||||
if allowOrigin == defaultMatchAll {
|
||||
return origin, true
|
||||
}
|
||||
if strings.ToLower(allowOrigin) == matchOrigin {
|
||||
return origin, true
|
||||
}
|
||||
}
|
||||
|
||||
// Check pattern match
|
||||
for _, allowOriginPattern := range c.allowOriginPatterns {
|
||||
if allowOriginPattern.declaredPattern == defaultMatchAll || allowOriginPattern.pattern.MatchString(matchOrigin) {
|
||||
return origin, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (c *CorsConfig) checkHeaders(requestHeaders string) (string, bool) {
|
||||
if len(c.allowHeaders) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if len(requestHeaders) == 0 {
|
||||
return strings.Join(c.allowHeaders, ","), true
|
||||
}
|
||||
|
||||
// Return all request headers when allowHeaders contains *
|
||||
if c.allowHeaders[0] == defaultMatchAll {
|
||||
return requestHeaders, true
|
||||
}
|
||||
|
||||
checkHeaders := strings.Split(requestHeaders, ",")
|
||||
// Each request header should be existed in allowHeaders configuration
|
||||
for _, h := range checkHeaders {
|
||||
isExist := false
|
||||
for _, allowHeader := range c.allowHeaders {
|
||||
if strings.ToLower(h) == strings.ToLower(allowHeader) {
|
||||
isExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isExist {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(c.allowHeaders, ","), true
|
||||
}
|
||||
|
||||
func (c *CorsConfig) checkMethods(requestMethod string) (string, bool) {
|
||||
if len(requestMethod) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Find method existed in allowMethods configuration
|
||||
for _, method := range c.allowMethods {
|
||||
if method == defaultMatchAll {
|
||||
return defaultAllAllowMethods, true
|
||||
}
|
||||
if strings.ToLower(method) == strings.ToLower(requestMethod) {
|
||||
return strings.Join(c.allowMethods, ","), true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (c *CorsConfig) isPreFlight(origin, method, controllerRequestMethod string) bool {
|
||||
return len(origin) > 0 && strings.ToLower(method) == strings.ToLower(HttpMethodOptions) && len(controllerRequestMethod) > 0
|
||||
}
|
||||
|
||||
func (c *CorsConfig) isCorsRequest(scheme, host, origin string) bool {
|
||||
if len(origin) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
url, err := url.Parse(strings.TrimSpace(origin))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check scheme
|
||||
if strings.ToLower(scheme) != strings.ToLower(url.Scheme) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check host and port
|
||||
port := ""
|
||||
originPort := ""
|
||||
originHost := ""
|
||||
host, port = c.getHostAndPort(scheme, host)
|
||||
originHost, originPort = c.getHostAndPort(url.Scheme, url.Host)
|
||||
if host != originHost || port != originPort {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *CorsConfig) getHostAndPort(scheme string, host string) (string, string) {
|
||||
// Get host and port
|
||||
scheme = strings.ToLower(scheme)
|
||||
host = strings.ToLower(host)
|
||||
port := ""
|
||||
hosts := strings.Split(host, ":")
|
||||
if len(hosts) > 1 {
|
||||
host = hosts[0]
|
||||
port = hosts[1]
|
||||
}
|
||||
// Get default port according scheme
|
||||
if len(port) == 0 && scheme == protocolHttpName {
|
||||
port = protocolHttpPort
|
||||
}
|
||||
if len(port) == 0 && scheme == protocolHttpsName {
|
||||
port = protocolHttpsPort
|
||||
}
|
||||
return host, port
|
||||
}
|
||||
408
plugins/wasm-go/extensions/cors/config/cors_config_test.go
Normal file
408
plugins/wasm-go/extensions/cors/config/cors_config_test.go
Normal file
@@ -0,0 +1,408 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCorsConfig_getHostAndPort(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scheme string
|
||||
host string
|
||||
wantHost string
|
||||
wantPort string
|
||||
}{
|
||||
{
|
||||
name: "http without port",
|
||||
scheme: "http",
|
||||
host: "http.example.com",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "80",
|
||||
},
|
||||
{
|
||||
name: "https without port",
|
||||
scheme: "https",
|
||||
host: "http.example.com",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "443",
|
||||
},
|
||||
|
||||
{
|
||||
name: "http with port and case insensitive",
|
||||
scheme: "hTTp",
|
||||
host: "hTTp.Example.com:8080",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "8080",
|
||||
},
|
||||
|
||||
{
|
||||
name: "https with port and case insensitive",
|
||||
scheme: "hTTps",
|
||||
host: "hTTp.Example.com:8080",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "8080",
|
||||
},
|
||||
|
||||
{
|
||||
name: "protocal is not http",
|
||||
scheme: "wss",
|
||||
host: "hTTp.Example.com",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "",
|
||||
},
|
||||
|
||||
{
|
||||
name: "protocal is not http",
|
||||
scheme: "wss",
|
||||
host: "hTTp.Example.com:8080",
|
||||
wantHost: "http.example.com",
|
||||
wantPort: "8080",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{}
|
||||
host, port := c.getHostAndPort(tt.scheme, tt.host)
|
||||
assert.Equal(t, tt.wantHost, host)
|
||||
assert.Equal(t, tt.wantPort, port)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_isCorsRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
scheme string
|
||||
host string
|
||||
origin string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "blank origin",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "normal equal case with space and case ",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: "http://hTTPbin.Example.com",
|
||||
want: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "cors request with port diff",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: " http://httpbin.example.com:8080 ",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "cors request with scheme diff",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: " https://HTTPpbin.Example.com ",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "cors request with host diff",
|
||||
scheme: "http",
|
||||
host: "httpbin.example.com",
|
||||
origin: " http://HTTPpbin.Example.org ",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{}
|
||||
assert.Equalf(t, tt.want, c.isCorsRequest(tt.scheme, tt.host, tt.origin), "isCorsRequest(%v, %v, %v)", tt.scheme, tt.host, tt.origin)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_isPreFlight(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
origin string
|
||||
method string
|
||||
controllerRequestMethod string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "blank case",
|
||||
origin: "",
|
||||
method: "",
|
||||
controllerRequestMethod: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "normal case",
|
||||
origin: "http://httpbin.example.com",
|
||||
method: "Options",
|
||||
controllerRequestMethod: "PUT",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "bad case with diff method",
|
||||
origin: "http://httpbin.example.com",
|
||||
method: "GET",
|
||||
controllerRequestMethod: "PUT",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{}
|
||||
assert.Equalf(t, tt.want, c.isPreFlight(tt.origin, tt.method, tt.controllerRequestMethod), "isPreFlight(%v, %v, %v)", tt.origin, tt.method, tt.controllerRequestMethod)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_checkMethods(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
allowMethods []string
|
||||
requestMethod string
|
||||
wantMethods string
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "default *",
|
||||
allowMethods: []string{"*"},
|
||||
requestMethod: "GET",
|
||||
wantMethods: defaultAllAllowMethods,
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "normal allow case",
|
||||
allowMethods: []string{"GET", "PUT", "HEAD"},
|
||||
requestMethod: "get",
|
||||
wantMethods: "GET,PUT,HEAD",
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "forbidden case",
|
||||
allowMethods: []string{"GET", "PUT", "HEAD"},
|
||||
requestMethod: "POST",
|
||||
wantMethods: "",
|
||||
wantOk: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "blank method",
|
||||
allowMethods: []string{"GET", "PUT", "HEAD"},
|
||||
requestMethod: "",
|
||||
wantMethods: "",
|
||||
wantOk: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{
|
||||
allowMethods: tt.allowMethods,
|
||||
}
|
||||
allowMethods, allowOk := c.checkMethods(tt.requestMethod)
|
||||
assert.Equalf(t, tt.wantMethods, allowMethods, "checkMethods(%v)", tt.requestMethod)
|
||||
assert.Equalf(t, tt.wantOk, allowOk, "checkMethods(%v)", tt.requestMethod)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_checkHeaders(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
allowHeaders []string
|
||||
requestHeaders string
|
||||
wantHeaders string
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "not pre-flight",
|
||||
allowHeaders: []string{"Content-Type", "Authorization"},
|
||||
requestHeaders: "",
|
||||
wantHeaders: "Content-Type,Authorization",
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "blank allowheaders case 1",
|
||||
allowHeaders: []string{},
|
||||
requestHeaders: "",
|
||||
wantHeaders: "",
|
||||
wantOk: false,
|
||||
},
|
||||
{
|
||||
name: "blank allowheaders case 2",
|
||||
requestHeaders: "Authorization",
|
||||
wantHeaders: "",
|
||||
wantOk: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "allowheaders *",
|
||||
allowHeaders: []string{"*"},
|
||||
requestHeaders: "Content-Type,Authorization",
|
||||
wantHeaders: "Content-Type,Authorization",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "allowheader values 1",
|
||||
allowHeaders: []string{"Content-Type", "Authorization"},
|
||||
requestHeaders: "Content-Type,Authorization",
|
||||
wantHeaders: "Content-Type,Authorization",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "allowheader values 2",
|
||||
allowHeaders: []string{"Content-Type", "Authorization"},
|
||||
requestHeaders: "",
|
||||
wantHeaders: "Content-Type,Authorization",
|
||||
wantOk: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{
|
||||
allowHeaders: tt.allowHeaders,
|
||||
}
|
||||
allowHeaders, allowOk := c.checkHeaders(tt.requestHeaders)
|
||||
assert.Equalf(t, tt.wantHeaders, allowHeaders, "checkHeaders(%v)", tt.requestHeaders)
|
||||
assert.Equalf(t, tt.wantOk, allowOk, "checkHeaders(%v)", tt.requestHeaders)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsConfig_checkOrigin(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
allowOrigins []string
|
||||
allowOriginPatterns []OriginPattern
|
||||
origin string
|
||||
wantOrigin string
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "allowOrigins *",
|
||||
allowOrigins: []string{defaultMatchAll},
|
||||
allowOriginPatterns: []OriginPattern{},
|
||||
origin: "http://Httpbin.Example.COM",
|
||||
wantOrigin: "http://Httpbin.Example.COM",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "allowOrigins exact match case 1",
|
||||
allowOrigins: []string{"http://httpbin.example.com"},
|
||||
allowOriginPatterns: []OriginPattern{},
|
||||
origin: "http://HTTPBin.EXample.COM",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM",
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "allowOrigins exact match case 2",
|
||||
allowOrigins: []string{"https://httpbin.example.com"},
|
||||
allowOriginPatterns: []OriginPattern{},
|
||||
origin: "http://HTTPBin.EXample.COM",
|
||||
wantOrigin: "",
|
||||
wantOk: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match with *",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("*"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match case with any port",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[*]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM",
|
||||
wantOk: true,
|
||||
},
|
||||
{
|
||||
name: "OriginPattern pattern match case with any port",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[*]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM:10000",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM:10000",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match case with specail port 1",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM:10000",
|
||||
wantOrigin: "",
|
||||
wantOk: false,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match case with specail port 2",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.COM:9090",
|
||||
wantOrigin: "http://HTTPBin.EXample.COM:9090",
|
||||
wantOk: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "OriginPattern pattern match case with specail port 3",
|
||||
allowOrigins: []string{},
|
||||
allowOriginPatterns: []OriginPattern{
|
||||
newOriginPatternFromString("http://*.example.com:[8080,9090]"),
|
||||
},
|
||||
origin: "http://HTTPBin.EXample.org:9090",
|
||||
wantOrigin: "",
|
||||
wantOk: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &CorsConfig{
|
||||
allowOrigins: tt.allowOrigins,
|
||||
allowOriginPatterns: tt.allowOriginPatterns,
|
||||
}
|
||||
allowOrigin, allowOk := c.checkOrigin(tt.origin)
|
||||
assert.Equalf(t, tt.wantOrigin, allowOrigin, "checkOrigin(%v)", tt.origin)
|
||||
assert.Equalf(t, tt.wantOk, allowOk, "checkOrigin(%v)", tt.origin)
|
||||
})
|
||||
}
|
||||
}
|
||||
67
plugins/wasm-go/extensions/cors/cors.yaml
Normal file
67
plugins/wasm-go/extensions/cors/cors.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
apiVersion: networking.higress.io/v1
|
||||
kind: McpBridge
|
||||
metadata:
|
||||
name: mcp-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
registries:
|
||||
- domain: httpbin.org
|
||||
name: httpbin
|
||||
port: 80
|
||||
type: dns
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
higress.io/destination: httpbin.dns
|
||||
higress.io/upstream-vhost: "httpbin.org"
|
||||
higress.io/backend-protocol: HTTP
|
||||
name: ingress-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: httpbin.example.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
resource:
|
||||
apiGroup: networking.higress.io
|
||||
kind: McpBridge
|
||||
name: mcp-cors-httpbin
|
||||
path: /
|
||||
pathType: Prefix
|
||||
---
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: wasm-cors-httpbin
|
||||
namespace: higress-system
|
||||
spec:
|
||||
defaultConfigDisable: true
|
||||
matchRules:
|
||||
- config:
|
||||
allow_origins:
|
||||
- http://httpbin.example.net
|
||||
allow_origin_patterns:
|
||||
- http://*.example.com:[*]
|
||||
- http://*.example.org:[9090,8080]
|
||||
allow_methods:
|
||||
- GET
|
||||
- POST
|
||||
- PATCH
|
||||
allow_headers:
|
||||
- Content-Type
|
||||
- Token
|
||||
- Authorization
|
||||
expose_headers:
|
||||
- X-Custom-Header
|
||||
- X-Env-UTM
|
||||
allow_credentials: true
|
||||
max_age: 3600
|
||||
configDisable: false
|
||||
ingress:
|
||||
- ingress-cors-httpbin
|
||||
url: oci://docker.io/2456868764/cors:1.0.0
|
||||
imagePullPolicy: Always
|
||||
78
plugins/wasm-go/extensions/cors/envoy.yaml
Normal file
78
plugins/wasm-go/extensions/cors/envoy.yaml
Normal file
@@ -0,0 +1,78 @@
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: main
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 18000
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
stat_prefix: ingress_http
|
||||
codec_type: auto
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains:
|
||||
- "httpbin.example.com"
|
||||
routes:
|
||||
- match:
|
||||
prefix: "/"
|
||||
route:
|
||||
cluster: httpbin
|
||||
http_filters:
|
||||
- name: envoy.filters.http.wasm
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
|
||||
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
|
||||
value:
|
||||
config:
|
||||
configuration:
|
||||
"@type": type.googleapis.com/google.protobuf.StringValue
|
||||
value: |-
|
||||
{
|
||||
"allow_origins": ["http://httpbin.example.net"],
|
||||
"allow_origin_patterns": ["http://*.example.com:[*]", "http://*.example.org:[9090,8080]"],
|
||||
"allow_methods": ["GET","PUT","POST", "PATCH", "HEAD", "OPTIONS"],
|
||||
"allow_credentials": true,
|
||||
"allow_headers":["Content-Type", "Token","Authorization"],
|
||||
"expose_headers":["X-Custom-Header"],
|
||||
"max_age": 3600
|
||||
}
|
||||
vm_config:
|
||||
runtime: "envoy.wasm.runtime.v8"
|
||||
code:
|
||||
local:
|
||||
filename: "./main.wasm"
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
|
||||
|
||||
clusters:
|
||||
- name: httpbin
|
||||
connect_timeout: 0.5s
|
||||
type: STRICT_DNS
|
||||
lb_policy: ROUND_ROBIN
|
||||
dns_refresh_rate: 5s
|
||||
dns_lookup_family: V4_ONLY
|
||||
load_assignment:
|
||||
cluster_name: httpbin
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: httpbin.org
|
||||
port_value: 80
|
||||
|
||||
|
||||
admin:
|
||||
access_log_path: "/dev/null"
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 8001
|
||||
21
plugins/wasm-go/extensions/cors/go.mod
Normal file
21
plugins/wasm-go/extensions/cors/go.mod
Normal file
@@ -0,0 +1,21 @@
|
||||
module cors
|
||||
|
||||
go 1.19
|
||||
|
||||
replace github.com/alibaba/higress/plugins/wasm-go => ../..
|
||||
|
||||
require (
|
||||
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230519024024-625c06e58f91
|
||||
github.com/stretchr/testify v1.8.3
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
|
||||
github.com/tidwall/gjson v1.14.4
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
20
plugins/wasm-go/extensions/cors/go.sum
Normal file
20
plugins/wasm-go/extensions/cors/go.sum
Normal file
@@ -0,0 +1,20 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 h1:kS7BvMKN+FiptV4pfwiNX8e3q14evxAWkhYbxt8EI1M=
|
||||
github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0/go.mod h1:qkW5MBz2jch2u8bS59wws65WC+Gtx3x0aPUX5JL7CXI=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
169
plugins/wasm-go/extensions/cors/main.go
Normal file
169
plugins/wasm-go/extensions/cors/main.go
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"cors/config"
|
||||
"fmt"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func main() {
|
||||
wrapper.SetCtx(
|
||||
"cors",
|
||||
wrapper.ParseConfigBy(parseConfig),
|
||||
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
|
||||
wrapper.ProcessRequestBodyBy(onHttpRequestBody),
|
||||
wrapper.ProcessResponseBodyBy(onHttpResponseBody),
|
||||
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
|
||||
)
|
||||
}
|
||||
|
||||
func parseConfig(json gjson.Result, corsConfig *config.CorsConfig, log wrapper.Log) error {
|
||||
log.Debug("parseConfig()")
|
||||
allowOrigins := json.Get("allow_origins").Array()
|
||||
for _, origin := range allowOrigins {
|
||||
if err := corsConfig.AddAllowOrigin(origin.String()); err != nil {
|
||||
log.Warnf("failed to AddAllowOrigin:%s, error:%v", origin, err)
|
||||
}
|
||||
}
|
||||
allowOriginPatterns := json.Get("allow_origin_patterns").Array()
|
||||
for _, pattern := range allowOriginPatterns {
|
||||
corsConfig.AddAllowOriginPattern(pattern.String())
|
||||
}
|
||||
allowMethods := json.Get("allow_methods").Array()
|
||||
for _, method := range allowMethods {
|
||||
corsConfig.AddAllowMethod(method.String())
|
||||
}
|
||||
allowHeaders := json.Get("allow_headers").Array()
|
||||
for _, header := range allowHeaders {
|
||||
corsConfig.AddAllowHeader(header.String())
|
||||
}
|
||||
exposeHeaders := json.Get("expose_headers").Array()
|
||||
for _, header := range exposeHeaders {
|
||||
corsConfig.AddExposeHeader(header.String())
|
||||
}
|
||||
allowCredentials := json.Get("allow_credentials").Bool()
|
||||
if err := corsConfig.SetAllowCredentials(allowCredentials); err != nil {
|
||||
log.Warnf("failed to set AllowCredentials error: %v", err)
|
||||
}
|
||||
maxAge := json.Get("max_age").Int()
|
||||
corsConfig.SetMaxAge(int(maxAge))
|
||||
|
||||
// Fill default values
|
||||
corsConfig.FillDefaultValues()
|
||||
log.Debugf("corsConfig:%+v", corsConfig)
|
||||
return nil
|
||||
}
|
||||
|
||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, corsConfig config.CorsConfig, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpRequestHeaders()")
|
||||
requestUrl, _ := proxywasm.GetHttpRequestHeader(":path")
|
||||
method, _ := proxywasm.GetHttpRequestHeader(":method")
|
||||
host := ctx.Host()
|
||||
scheme := ctx.Scheme()
|
||||
log.Debugf("scheme:%s, host:%s, method:%s, request:%s", scheme, host, method, requestUrl)
|
||||
// Get headers
|
||||
headers, _ := proxywasm.GetHttpRequestHeaders()
|
||||
// Process request
|
||||
httpCorsContext, err := corsConfig.Process(scheme, host, method, headers)
|
||||
if err != nil {
|
||||
log.Warnf("failed to process %s : %v", requestUrl, err)
|
||||
return types.ActionContinue
|
||||
}
|
||||
log.Debugf("Process httpCorsContext:%+v", httpCorsContext)
|
||||
// Set HttpContext
|
||||
ctx.SetContext(config.HttpContextKey, httpCorsContext)
|
||||
|
||||
// Response forbidden when it is not valid cors request
|
||||
if !httpCorsContext.IsValid {
|
||||
headers := make([][2]string, 0)
|
||||
headers = append(headers, [2]string{config.HeaderPluginTrace, "trace"})
|
||||
proxywasm.SendHttpResponse(403, headers, []byte("Invalid CORS request"), -1)
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
// Response directly when it is cors preflight request
|
||||
if httpCorsContext.IsPreFlight {
|
||||
headers := make([][2]string, 0)
|
||||
headers = append(headers, [2]string{config.HeaderPluginTrace, "trace"})
|
||||
proxywasm.SendHttpResponse(200, headers, nil, -1)
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpRequestBody(ctx wrapper.HttpContext, corsConfig config.CorsConfig, body []byte, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpRequestBody()")
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpResponseHeaders(ctx wrapper.HttpContext, corsConfig config.CorsConfig, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpResponseHeaders()")
|
||||
// Remove trace header if existed
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderPluginTrace)
|
||||
// Remove upstream cors response headers if existed
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowOrigin)
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowMethods)
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlExposeHeaders)
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlAllowCredentials)
|
||||
proxywasm.RemoveHttpResponseHeader(config.HeaderAccessControlMaxAge)
|
||||
// Add debug header
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderPluginDebug, corsConfig.GetVersion())
|
||||
|
||||
// Restore httpCorsContext from HttpContext
|
||||
httpCorsContext, ok := ctx.GetContext(config.HttpContextKey).(config.HttpCorsContext)
|
||||
if !ok {
|
||||
log.Debug("restore httpCorsContext from HttpContext error")
|
||||
return types.ActionContinue
|
||||
}
|
||||
log.Debugf("Restore httpCorsContext:%+v", httpCorsContext)
|
||||
|
||||
// Skip which it is not valid or not cors request
|
||||
if !httpCorsContext.IsValid || !httpCorsContext.IsCorsRequest {
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
// Add Cors headers when it is cors and valid request
|
||||
if len(httpCorsContext.AllowOrigin) > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowOrigin, httpCorsContext.AllowOrigin)
|
||||
}
|
||||
if len(httpCorsContext.AllowMethods) > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowMethods, httpCorsContext.AllowMethods)
|
||||
}
|
||||
if len(httpCorsContext.AllowHeaders) > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowHeaders, httpCorsContext.AllowHeaders)
|
||||
}
|
||||
if len(httpCorsContext.ExposeHeaders) > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlExposeHeaders, httpCorsContext.ExposeHeaders)
|
||||
}
|
||||
if httpCorsContext.AllowCredentials {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlAllowCredentials, "true")
|
||||
}
|
||||
if httpCorsContext.MaxAge > 0 {
|
||||
proxywasm.AddHttpResponseHeader(config.HeaderAccessControlMaxAge, fmt.Sprintf("%d", httpCorsContext.MaxAge))
|
||||
}
|
||||
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
func onHttpResponseBody(ctx wrapper.HttpContext, corsConfig config.CorsConfig, body []byte, log wrapper.Log) types.Action {
|
||||
log.Debug("onHttpResponseBody()")
|
||||
return types.ActionContinue
|
||||
}
|
||||
3
plugins/wasm-go/extensions/waf/Dockerfile
Normal file
3
plugins/wasm-go/extensions/waf/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM scratch
|
||||
|
||||
COPY local/main.wasm /plugin.wasm
|
||||
52
plugins/wasm-go/extensions/waf/README.md
Normal file
52
plugins/wasm-go/extensions/waf/README.md
Normal 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_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
|
||||
37
plugins/wasm-go/extensions/waf/go.mod
Normal file
37
plugins/wasm-go/extensions/waf/go.mod
Normal 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
|
||||
)
|
||||
65
plugins/wasm-go/extensions/waf/go.sum
Normal file
65
plugins/wasm-go/extensions/waf/go.sum
Normal 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=
|
||||
14
plugins/wasm-go/extensions/waf/init_tinygo.go
Normal file
14
plugins/wasm-go/extensions/waf/init_tinygo.go
Normal 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
|
||||
}
|
||||
3
plugins/wasm-go/extensions/waf/local/Dockerfile
Normal file
3
plugins/wasm-go/extensions/waf/local/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM liuxr25/flask-helloworld:latest
|
||||
|
||||
COPY app.py /work/app.py
|
||||
14
plugins/wasm-go/extensions/waf/local/app.py
Normal file
14
plugins/wasm-go/extensions/waf/local/app.py
Normal 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)
|
||||
96
plugins/wasm-go/extensions/waf/local/docker-compose.yaml
Normal file
96
plugins/wasm-go/extensions/waf/local/docker-compose.yaml
Normal 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:
|
||||
143
plugins/wasm-go/extensions/waf/local/envoy-config.yaml
Normal file
143
plugins/wasm-go/extensions/waf/local/envoy-config.yaml
Normal 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
|
||||
16
plugins/wasm-go/extensions/waf/mage.go
Normal file
16
plugins/wasm-go/extensions/waf/mage.go
Normal 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())
|
||||
}
|
||||
16
plugins/wasm-go/extensions/waf/magefiles/go.mod
Normal file
16
plugins/wasm-go/extensions/waf/magefiles/go.mod
Normal 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
|
||||
)
|
||||
21
plugins/wasm-go/extensions/waf/magefiles/go.sum
Normal file
21
plugins/wasm-go/extensions/waf/magefiles/go.sum
Normal 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=
|
||||
81
plugins/wasm-go/extensions/waf/magefiles/loadtest.go
Normal file
81
plugins/wasm-go/extensions/waf/magefiles/loadtest.go
Normal 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
|
||||
}
|
||||
271
plugins/wasm-go/extensions/waf/magefiles/magefile.go
Normal file
271
plugins/wasm-go/extensions/waf/magefiles/magefile.go
Normal 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
|
||||
}
|
||||
14
plugins/wasm-go/extensions/waf/main.go
Normal file
14
plugins/wasm-go/extensions/waf/main.go
Normal 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()
|
||||
}
|
||||
79
plugins/wasm-go/extensions/waf/wasmplugin/fs.go
Normal file
79
plugins/wasm-go/extensions/waf/wasmplugin/fs.go
Normal 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
|
||||
}
|
||||
339
plugins/wasm-go/extensions/waf/wasmplugin/plugin.go
Normal file
339
plugins/wasm-go/extensions/waf/wasmplugin/plugin.go
Normal 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")
|
||||
}
|
||||
253
plugins/wasm-go/extensions/waf/wasmplugin/rules/coraza-demo.conf
Normal file
253
plugins/wasm-go/extensions/waf/wasmplugin/rules/coraza-demo.conf
Normal 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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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'"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
Reference in New Issue
Block a user