mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 20:57:32 +08:00
Go WAF Plugin (#400)
This commit is contained in:
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
|
||||
}
|
||||
Reference in New Issue
Block a user