mirror of
https://github.com/alibaba/higress.git
synced 2026-02-24 20:50:51 +08:00
172 lines
5.4 KiB
Go
172 lines
5.4 KiB
Go
package wasmplugin
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"strconv"
|
|
|
|
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
|
ctypes "github.com/corazawaf/coraza/v3/types"
|
|
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
|
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
|
)
|
|
|
|
const noGRPCStream int32 = -1
|
|
const replaceResponseBody int = 10
|
|
|
|
// retrieveAddressInfo retrieves address properties from the proxy
|
|
// Expected targets are "source" or "destination"
|
|
// Envoy ref: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes#connection-attributes
|
|
func retrieveAddressInfo(logger wrapper.Log, target string) (string, int) {
|
|
var targetIP, targetPortStr string
|
|
var targetPort int
|
|
targetAddressRaw, err := proxywasm.GetProperty([]string{target, "address"})
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("Failed to get %s address", target))
|
|
} else {
|
|
targetIP, targetPortStr, err = net.SplitHostPort(string(targetAddressRaw))
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("Failed to parse %s address", target))
|
|
}
|
|
}
|
|
targetPortRaw, err := proxywasm.GetProperty([]string{target, "port"})
|
|
if err == nil {
|
|
targetPort, err = parsePort(targetPortRaw)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("Failed to parse %s port", target))
|
|
}
|
|
} else if targetPortStr != "" {
|
|
// If GetProperty fails we rely on the port inside the Address property
|
|
// Mostly useful for proxies other than Envoy
|
|
targetPort, err = strconv.Atoi(targetPortStr)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("Failed to get %s port", target))
|
|
}
|
|
}
|
|
return targetIP, targetPort
|
|
}
|
|
|
|
// parsePort converts port, retrieved as little-endian bytes, into int
|
|
func parsePort(b []byte) (int, error) {
|
|
// Port attribute ({"source", "port"}) is populated as uint64 (8 byte)
|
|
// Ref: https://github.com/envoyproxy/envoy/blob/1b3da361279a54956f01abba830fc5d3a5421828/source/common/network/utility.cc#L201
|
|
if len(b) < 8 {
|
|
return 0, errors.New("port bytes not found")
|
|
}
|
|
// 0 < Port number <= 65535, therefore the retrieved value should never exceed 16 bits
|
|
// and correctly fit int (at least 32 bits in size)
|
|
unsignedInt := binary.LittleEndian.Uint64(b)
|
|
if unsignedInt > math.MaxInt32 {
|
|
return 0, errors.New("port conversion error")
|
|
}
|
|
return int(unsignedInt), nil
|
|
}
|
|
|
|
// parseServerName parses :authority pseudo-header in order to retrieve the
|
|
// virtual host.
|
|
func parseServerName(logger wrapper.Log, authority string) string {
|
|
host, _, err := net.SplitHostPort(authority)
|
|
if err != nil {
|
|
// missing port or bad format
|
|
logger.Debug("Failed to parse server name from authority")
|
|
host = authority
|
|
}
|
|
return host
|
|
}
|
|
|
|
func handleInterruption(ctx wrapper.HttpContext, phase string, interruption *ctypes.Interruption, log wrapper.Log) types.Action {
|
|
if ctx.GetContext("interruptionHandled").(bool) {
|
|
// handleInterruption should never be called more than once
|
|
panic("Interruption already handled")
|
|
}
|
|
|
|
ctx.SetContext("interruptionHandled", true)
|
|
if phase == "http_response_body" {
|
|
return replaceResponseBodyWhenInterrupted(log, replaceResponseBody)
|
|
}
|
|
|
|
statusCode := interruption.Status
|
|
//log.Infof("Status code is %d", statusCode)
|
|
if statusCode == 0 {
|
|
statusCode = 403
|
|
}
|
|
if err := proxywasm.SendHttpResponseWithDetail(uint32(statusCode), "waf", nil, nil, noGRPCStream); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// SendHttpResponse must be followed by ActionPause in order to stop malicious content
|
|
return types.ActionPause
|
|
}
|
|
|
|
// replaceResponseBodyWhenInterrupted address an interruption raised during phase 4.
|
|
// At this phase, response headers are already sent downstream, therefore an interruption
|
|
// can not change anymore the status code, but only tweak the response body
|
|
func replaceResponseBodyWhenInterrupted(logger wrapper.Log, bodySize int) types.Action {
|
|
// TODO(M4tteoP): Update response body interruption logic after https://github.com/corazawaf/coraza-proxy-wasm/issues/26
|
|
// Currently returns a body filled with null bytes that replaces the sensitive data potentially leaked
|
|
err := proxywasm.ReplaceHttpResponseBody(bytes.Repeat([]byte("\x00"), bodySize))
|
|
if err != nil {
|
|
logger.Error("Failed to replace response body")
|
|
return types.ActionContinue
|
|
}
|
|
logger.Warn("Response body intervention occurred: body replaced")
|
|
return types.ActionContinue
|
|
}
|
|
|
|
func logError(error ctypes.MatchedRule) {
|
|
msg := error.ErrorLog(0)
|
|
switch error.Rule().Severity() {
|
|
case ctypes.RuleSeverityEmergency:
|
|
proxywasm.LogCritical(msg)
|
|
case ctypes.RuleSeverityAlert:
|
|
proxywasm.LogCritical(msg)
|
|
case ctypes.RuleSeverityCritical:
|
|
proxywasm.LogCritical(msg)
|
|
case ctypes.RuleSeverityError:
|
|
proxywasm.LogError(msg)
|
|
case ctypes.RuleSeverityWarning:
|
|
proxywasm.LogWarn(msg)
|
|
case ctypes.RuleSeverityNotice:
|
|
proxywasm.LogInfo(msg)
|
|
case ctypes.RuleSeverityInfo:
|
|
proxywasm.LogInfo(msg)
|
|
case ctypes.RuleSeverityDebug:
|
|
proxywasm.LogDebug(msg)
|
|
}
|
|
}
|
|
|
|
func isWebSocketRequest() bool {
|
|
if value, err := proxywasm.GetHttpRequestHeader("Upgrade"); err == nil {
|
|
if value == "websocket" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isSSERequest() bool {
|
|
if value, err := proxywasm.GetHttpRequestHeader("Accept"); err == nil {
|
|
if value == "text/event-stream" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isGrpcRequest() bool {
|
|
if value, err := proxywasm.GetHttpRequestHeader("Content-Type"); err == nil {
|
|
if value == "application/grpc" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func ignoreBody() bool {
|
|
return isWebSocketRequest() || isSSERequest() || isGrpcRequest()
|
|
}
|