Skip to content

Commit

Permalink
Merge branch 'master' into 683-ipv6
Browse files Browse the repository at this point in the history
  • Loading branch information
malud authored May 9, 2023
2 parents 338de33 + f30b90e commit 9f17aef
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ Unreleased changes are available as `avenga/couper:edge` container.
* IPv6 support via [`-bind-address`](https://docs.couper.io/configuration/command-line#network-options) option. ([#752](https://github.com/avenga/couper/pull/752))
* If Couper starts with `-watch` CLI flag, watch referenced files, too ([#747](https://github.com/avenga/couper/pull/747))

* **Changed**
* More specific error log messages for [`oauth2`](https://docs.couper.io/configuration/block/oauth2) and [`beta_token_request`](https://docs.couper.io/configuration/block/token_request) token request errors ([#755](https://github.com/avenga/couper/pull/755))

* **Fixed**
* Erroneously sending an empty [`Server-Timing` header](https://docs.couper.io/configuration/command-line#oberservation-options) ([#700](https://github.com/avenga/couper/pull/700))
* `WWW-Authenticate` header `realm` param value for [`basic_auth`](https://docs.couper.io/configuration/block/basic_auth) ([#715](https://github.com/avenga/couper/pull/715))
* [`Server-Timing` header](https://docs.couper.io/configuration/block/settings) only reporting last requests/proxies of [endpoint sequences](https://docs.couper.io/configuration/block/endpoint#endpoint-sequence) ([#751](https://github.com/avenga/couper/pull/751))
* Selecting of appropriate [error handler](https://docs.couper.io/configuration/block/error_handler) in two cases ([#753](https://github.com/avenga/couper/pull/753))

* **Dependencies**
* build with go 1.20 ([#745](https://github.com/avenga/couper/pull/745))
Expand Down
16 changes: 14 additions & 2 deletions config/runtime/error_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package runtime
import (
"net/http"
"reflect"
"sort"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
Expand Down Expand Up @@ -35,15 +36,26 @@ func newErrorHandler(ctx *hcl.EvalContext, conf *config.Couper, opts *protectedO
// expand super-kinds:
// * set super-kind error handler for mapped sub-kinds, if no error handler for this sub-kind is already set
// * remove super-kind error handler for super-kind
for superKind, subKinds := range superKindMap {
// for superKind, subKinds := range superKindMap {
keys := make([]string, len(superKindMap))
i := 0
for k := range superKindMap {
keys[i] = k
i++
}
sort.Sort(sort.Reverse(sort.StringSlice(keys)))
for _, superKind := range keys {
subKinds := superKindMap[superKind]
if skHandler, skExists := handlersPerKind[superKind]; skExists {
for _, subKind := range subKinds {
if _, exists := handlersPerKind[subKind]; !exists {
handlersPerKind[subKind] = skHandler
}
}

delete(handlersPerKind, superKind)
if superKind == "*" {
delete(handlersPerKind, superKind)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion handler/transport/oauth2_req_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func (oa *OAuth2ReqAuth) GetToken(req *http.Request) error {

tokenResponseData, token, err := oa.oauth2Client.GetTokenResponse(req.Context(), formParams)
if err != nil {
return requestError.Message("token request failed") // don't propagate token request roundtrip error
return requestError.Message("token request failed").With(err)
}

oa.updateAccessToken(token, tokenResponseData)
Expand Down
4 changes: 2 additions & 2 deletions handler/transport/token_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (t *TokenRequest) GetToken(req *http.Request) error {
)
token, ttl, err = t.requestToken(req)
if err != nil {
return errors.Request.Label(t.config.Name).With(err)
return errors.Request.Label(t.config.Name).Message("token request failed").With(err)
}

t.memStore.Set(t.storageKey, token, ttl)
Expand All @@ -85,7 +85,7 @@ func (t *TokenRequest) requestToken(req *http.Request) (string, int64, error) {
outreq, _ := http.NewRequestWithContext(ctx, req.Method, "", nil)
result := t.reqProducer.Produce(outreq)
if result.Err != nil {
return "", 0, fmt.Errorf("token request failed") // don't propagate token request roundtrip error
return "", 0, result.Err
}

// obtain synced and already read beresp value; map to context variables
Expand Down
56 changes: 56 additions & 0 deletions server/http_error_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,62 @@ func TestAccessControl_ErrorHandler_Permissions(t *testing.T) {

}

func TestErrorHandler_SuperKind(t *testing.T) {
client := test.NewHTTPClient()

helper := test.New(t)
// valid token, but lacking permissions claim
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.qSLnmYgnkcOjxlOjFhUHQpCfTQ5elzKY3Mq6gRVT4iI"

shutdown, _ := newCouper("testdata/integration/error_handler/09_couper.hcl", helper)
defer shutdown()

type testcase struct {
name string
path string
sendToken bool
expFrom string
}

for _, tc := range []testcase{
{"ac: handler for *", "/ac1", true, "*"},
{"ac: handlers for *, access_control", "/ac2", true, "access_control"},
{"ac: handlers for *, access_control, insufficient_permissions", "/ac3", true, "insufficient_permissions"},
{"ep: handler for *", "/ep1", false, "*"},
{"ep: handlers for *, endpoint", "/ep2", false, "endpoint"},
{"ep: handlers for *, endpoint, unexpected_status", "/ep3", false, "unexpected_status"},
{"be: handler for *", "/be1", false, "*"},
{"be: handlers for *, backend", "/be2", false, "backend"},
{"be: handlers for *, backend, backend_timeout", "/be3", false, "backend_timeout"},
{"be: handlers for backend, backend_timeout", "/be4", false, "backend_timeout"},
{"be: handler for backend", "/be5", false, "backend"},
{"be dial error: handlers for *, backend", "/be-dial", false, "backend"},
} {
t.Run(tc.name, func(st *testing.T) {
h := test.New(st)
req, err := http.NewRequest(http.MethodGet, "http://localhost:8080"+tc.path, nil)
h.Must(err)

if tc.sendToken {
// not needed for non-ac tests
req.Header.Set("Authorization", "Bearer "+token)
}

res, err := client.Do(req)
h.Must(err)

if res.StatusCode != http.StatusNoContent {
st.Errorf("Expected status code: %d, got: %d", http.StatusNoContent, res.StatusCode)
}
from := res.Header.Get("From")
if from != tc.expFrom {
st.Errorf("Expected From response header: %q, got: %q", tc.expFrom, from)
}
})
}

}

func Test_Panic_Multi_EH(t *testing.T) {
_, err := configload.LoadFile("testdata/settings/16_couper.hcl", "")

Expand Down
35 changes: 25 additions & 10 deletions server/http_oauth2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1067,20 +1067,35 @@ func TestOAuth2_Runtime_Errors(t *testing.T) {
helper.Must(werr)
return
}
if req.URL.Path == "/token/error" {
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusBadRequest)

body := []byte(`{
"error": "the_error",
"error_description": "the error description",
"error_uri": "https://as/error/uri"
}`)
_, werr := rw.Write(body)
helper.Must(werr)
return
}
rw.WriteHeader(http.StatusBadRequest)
}))
defer asOrigin.Close()

type testCase struct {
name string
filename string
path string
wantErrLog string
}

for _, tc := range []testCase{
{"null assertion", "17_couper.hcl", "backend error: be: request error: oauth2: assertion expression evaluates to null"},
{"non-string assertion", "18_couper.hcl", "backend error: be: request error: oauth2: assertion expression must evaluate to a string"},
{"token request error", "19_couper.hcl", "backend error: be: request error: oauth2: token request failed"},
{"null assertion", "17_couper.hcl", "/resource", "backend error: be: request error: oauth2: assertion expression evaluates to null"},
{"non-string assertion", "18_couper.hcl", "/resource", "backend error: be: request error: oauth2: assertion expression must evaluate to a string"},
{"token request error: connect error", "19_couper.hcl", "/resource", "backend error: be: request error: oauth2: token request failed: backend error: as_down: proxyconnect tcp: connecting to as_down '1.2.3.4:80' failed: dial tcp 127.0.0.1:9999: connect: connection refused"},
{"token request error: AS responding with error", "19_couper.hcl", "/other/resource", "backend error: be2: request error: oauth2: token request failed: error=the_error, error_description=the error description, error_uri=https://as/error/uri"},
} {
t.Run(tc.name, func(subT *testing.T) {
h := test.New(subT)
Expand All @@ -1089,7 +1104,7 @@ func TestOAuth2_Runtime_Errors(t *testing.T) {
h.Must(err)
defer shutdown()

req, err := http.NewRequest(http.MethodGet, "http://anyserver:8080/resource", nil)
req, err := http.NewRequest(http.MethodGet, "http://anyserver:8080"+tc.path, nil)
h.Must(err)

hook.Reset()
Expand Down Expand Up @@ -2169,14 +2184,14 @@ func TestTokenRequest_Runtime_Errors(t *testing.T) {
}

for _, tc := range []testCase{
{"token request error, handled by error handler", "01_token_request_error.hcl", http.StatusNoContent, "backend error: be: request error: tr: token request failed"},
{"token request error, handled by error handler", "01_token_request_error.hcl", http.StatusNoContent, "backend error: be: request error: tr: token request failed: backend error: down: proxyconnect tcp: connecting to down '1.2.3.4:80' failed: dial tcp 127.0.0.1:9999: connect: connection refused"},
{"token expression evaluation error", "02_token_request_error.hcl", http.StatusBadGateway, "couper-bytes.hcl:23,15-31: Call to unknown function; There is no function named \"evaluation_error\"."},
{"null token", "03_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: token expression evaluates to null"},
{"non-string token", "04_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: token expression must evaluate to a string"},
{"null token", "03_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: token request failed: token expression evaluates to null"},
{"non-string token", "04_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: token request failed: token expression must evaluate to a string"},
{"ttl expression evaluation error", "05_token_request_error.hcl", http.StatusBadGateway, "couper-bytes.hcl:24,13-29: Call to unknown function; There is no function named \"evaluation_error\"."},
{"null ttl", "06_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: ttl expression evaluates to null"},
{"non-string ttl", "07_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: ttl expression must evaluate to a string"},
{"non-duration ttl", "08_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: ttl: time: invalid duration \"no duration\""},
{"null ttl", "06_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: token request failed: ttl expression evaluates to null"},
{"non-string ttl", "07_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: token request failed: ttl expression must evaluate to a string"},
{"non-duration ttl", "08_token_request_error.hcl", http.StatusBadGateway, "backend error: be: request error: tr: token request failed: ttl: time: invalid duration \"no duration\""},
} {
t.Run(tc.name, func(subT *testing.T) {
h := test.New(subT)
Expand Down
Loading

0 comments on commit 9f17aef

Please sign in to comment.