diff --git a/config/configload/endpoint.go b/config/configload/endpoint.go index 91c121f48..a4dd22833 100644 --- a/config/configload/endpoint.go +++ b/config/configload/endpoint.go @@ -54,6 +54,12 @@ func refineEndpoints(definedBackends Backends, endpoints config.Endpoints, check endpointContent := bodyToContent(endpoint.Remain) + if check && endpoint.AllowedMethods != nil && len(endpoint.AllowedMethods) > 0 { + if err = validMethods(endpoint.AllowedMethods, &endpointContent.Attributes["allowed_methods"].Range); err != nil { + return err + } + } + proxies := endpointContent.Blocks.OfType(proxy) requests := endpointContent.Blocks.OfType(request) diff --git a/config/configload/load.go b/config/configload/load.go index 518a987f7..1df353b17 100644 --- a/config/configload/load.go +++ b/config/configload/load.go @@ -304,6 +304,12 @@ func LoadConfig(body hcl.Body, src []byte, filename string) (*config.Couper, err apiConfig.Name = apiBlock.Labels[0] } + if apiConfig.AllowedMethods != nil && len(apiConfig.AllowedMethods) > 0 { + if err = validMethods(apiConfig.AllowedMethods, &bodyToContent(apiConfig.Remain).Attributes["allowed_methods"].Range); err != nil { + return nil, err + } + } + err := refineEndpoints(definedBackends, apiConfig.Endpoints, true) if err != nil { return nil, err diff --git a/config/configload/validate.go b/config/configload/validate.go index 42aaec2bf..74b11428f 100644 --- a/config/configload/validate.go +++ b/config/configload/validate.go @@ -2,10 +2,15 @@ package configload import ( "fmt" + "regexp" "github.com/hashicorp/hcl/v2" ) +// https://datatracker.ietf.org/doc/html/rfc7231#section-4 +// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 +var methodRegExp = regexp.MustCompile("^[!#$%&'*+\\-.\\^_`|~0-9a-zA-Z]+$") + func validLabelName(name string, hr *hcl.Range) error { if !regexProxyRequestLabel.MatchString(name) { return hcl.Diagnostics{&hcl.Diagnostic{ @@ -53,3 +58,17 @@ func verifyBodyAttributes(content *hcl.BodyContent) error { } return nil } + +func validMethods(methods []string, hr *hcl.Range) error { + for _, method := range methods { + if !methodRegExp.MatchString(method) { + return hcl.Diagnostics{&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "method contains invalid character(s)", + Subject: hr, + }} + } + } + + return nil +} diff --git a/handler/middleware/allowed_methods.go b/handler/middleware/allowed_methods.go index 2a26be566..6f6dfc61e 100644 --- a/handler/middleware/allowed_methods.go +++ b/handler/middleware/allowed_methods.go @@ -1,9 +1,7 @@ package middleware import ( - "fmt" "net/http" - "regexp" "strings" ) @@ -17,10 +15,6 @@ var defaultAllowedMethods = []string{ http.MethodOptions, } -// https://datatracker.ietf.org/doc/html/rfc7231#section-4 -// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 -var methodRegExp = regexp.MustCompile("^[!#$%&'*+\\-.\\^_`|~0-9a-zA-Z]+$") - var _ http.Handler = &AllowedMethodsHandler{} type AllowedMethodsHandler struct { @@ -41,15 +35,11 @@ func NewAllowedMethodsHandler(allowedMethods []string, allowedHandler, notAllowe allowedMethods = defaultAllowedMethods } for _, method := range allowedMethods { - method = strings.TrimSpace(method) if method == "*" { for _, m := range defaultAllowedMethods { amh.allowedMethods[m] = struct{}{} } } else { - if !methodRegExp.Match([]byte(method)) { - return nil, fmt.Errorf("allowed_methods: invalid character in method %q", method) - } method = strings.ToUpper(method) amh.allowedMethods[method] = struct{}{} } diff --git a/main_test.go b/main_test.go index 95ce5353b..1d91a1e4a 100644 --- a/main_test.go +++ b/main_test.go @@ -42,8 +42,9 @@ func Test_realmain(t *testing.T) { {"non-existent log level via env /w file", []string{"couper", "run", "-f", base + "/log_altered.hcl"}, []string{"COUPER_LOG_LEVEL=test"}, `level=error msg="configuration error: missing 'server' block" build=dev`, 1}, {"-f w/o file", []string{"couper", "run", "-f"}, nil, `level=error msg="flag needs an argument: -f" build=dev`, 1}, {"undefined AC", []string{"couper", "run", "-f", base + "/04_couper.hcl"}, nil, `level=error msg="accessControl is not defined: undefined" build=dev`, 1}, - {"empty string in allowed_methods", []string{"couper", "run", "-f", base + "/13_couper.hcl"}, nil, `level=error msg="allowed_methods: invalid character in method \"\"" build=dev`, 1}, - {"invalid method in allowed_methods", []string{"couper", "run", "-f", base + "/14_couper.hcl"}, nil, `level=error msg="allowed_methods: invalid character in method \"in valid\"" build=dev`, 1}, + {"empty string in allowed_methods in endpoint", []string{"couper", "run", "-f", base + "/13_couper.hcl"}, nil, `level=error msg="13_couper.hcl:3,5-27: method contains invalid character(s); " build=dev`, 1}, + {"invalid method in allowed_methods in endpoint", []string{"couper", "run", "-f", base + "/14_couper.hcl"}, nil, `level=error msg="14_couper.hcl:3,5-35: method contains invalid character(s); " build=dev`, 1}, + {"invalid method in allowed_methods in api", []string{"couper", "run", "-f", base + "/15_couper.hcl"}, nil, `level=error msg="15_couper.hcl:3,5-35: method contains invalid character(s); " build=dev`, 1}, } for _, tt := range tests { t.Run(tt.name, func(subT *testing.T) { diff --git a/server/testdata/settings/15_couper.hcl b/server/testdata/settings/15_couper.hcl new file mode 100644 index 000000000..b05fe79f1 --- /dev/null +++ b/server/testdata/settings/15_couper.hcl @@ -0,0 +1,5 @@ +server { + api { + allowed_methods = ["in valid"] + } +}