Skip to content

Commit

Permalink
Store invalid backend response (#501)
Browse files Browse the repository at this point in the history
* test for invalid backend response in backend_responses; and for status in backend log

* return invalid response instead of nil

* create error beresp only for missing real beresp

* changelog entry

Co-authored-by: Joe Afflerbach <joe.afflerbach@avenga.com>
  • Loading branch information
johakoch and afflerbach authored May 10, 2022
1 parent 9a2e3d4 commit d7dfacf
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Unreleased changes are available as `avenga/couper:edge` container.
* Request methods are treated case-insensitively when comparing them to methods in the `allowed_methods` attribute of [`api`](./docs/REFERENCE.md#api-block) or [`endpoint`](./docs/REFERENCE.md#endpoint-block) blocks ([#478](https://github.com/avenga/couper/pull/478))
* Do not allow multiple `backend` blocks in `proxy` and `request` blocks ([#483](https://github.com/avenga/couper/pull/483))
* Panic if an [`error_handler` block](./docs/REFERENCE.md#error-handler-block) following another `error_handler` block has no label ([#486](https://github.com/avenga/couper/pull/486))
* Invalid (by [OpenAPI validation](./docs/REFERENCE.md#openapi-block)) backend response missing in [`backend_responses`](./docs/REFERENCE.md#backend_responses) ([#501](https://github.com/avenga/couper/pull/501))

* **Removed**
* support for `beta_oidc` block (use [`oidc` block](./docs/REFERENCE.md#oidc-block) instead) ([#475](https://github.com/avenga/couper/pull/475))
Expand Down
14 changes: 8 additions & 6 deletions handler/transport/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,15 @@ func (b *Backend) RoundTrip(req *http.Request) (*http.Response, error) {
}

if err != nil {
berespErr := &http.Response{
Request: req,
} // provide outreq (variable) on error cases
if beresp == nil {
beresp = &http.Response{
Request: req,
} // provide outreq (variable) on error cases
}
if varSync, ok := req.Context().Value(request.ContextVariablesSynced).(*eval.SyncedVariables); ok {
varSync.Set(berespErr)
varSync.Set(beresp)
}
return berespErr, err
return beresp, err
}

if retry, rerr := b.withRetryTokenRequest(req, beresp); rerr != nil {
Expand Down Expand Up @@ -226,7 +228,7 @@ func (b *Backend) openAPIValidate(req *http.Request, tc *Config, deadlineErr <-c
}

if err = b.openAPIValidator.ValidateResponse(beresp, requestValidationInput); err != nil {
return nil, errors.BackendOpenapiValidation.Label(b.name).With(err).Status(http.StatusBadGateway)
return beresp, errors.BackendOpenapiValidation.Label(b.name).With(err).Status(http.StatusBadGateway)
}

return beresp, nil
Expand Down
61 changes: 61 additions & 0 deletions server/http_error_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"testing"

logrustest "github.com/sirupsen/logrus/hooks/test"
"github.com/zclconf/go-cty/cty"

"github.com/avenga/couper/accesscontrol/jwt"
Expand Down Expand Up @@ -177,6 +178,66 @@ func TestErrorHandler_Backend(t *testing.T) {
}
}

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

shutdown, hook := newCouper("testdata/integration/error_handler/06_couper.hcl", test.New(t))
defer shutdown()

type testcase struct {
path string
expBody string
expStatus int
expBackendStatus int
expValidation string
}

for _, tc := range []testcase{
{"/anything", `{"req_path":"/anything","resp_ct":"application/json","resp_json_body_query":{},"resp_status":200}`, 418, 200, "status is not supported"},
} {
t.Run(tc.path, func(st *testing.T) {
helper := test.New(st)
hook.Reset()

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

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

if res.StatusCode != tc.expStatus {
st.Errorf("status code want: %d, got: %d", tc.expStatus, res.StatusCode)
}

resBytes, err := io.ReadAll(res.Body)
defer res.Body.Close()
helper.Must(err)

if !bytes.Contains(resBytes, []byte(tc.expBody)) {
st.Errorf("body\nwant: %s,\ngot: %s", tc.expBody, resBytes)
}

backendStatus, validation := getBackendLogStatusAndValidation(hook)
if backendStatus != tc.expBackendStatus {
st.Errorf("backend status want: %d, got: %d", tc.expBackendStatus, backendStatus)
}
if validation != tc.expValidation {
st.Errorf("validation want: %s, got: %s", tc.expValidation, validation)
}
})
}
}

func getBackendLogStatusAndValidation(hook *logrustest.Hook) (int, string) {
for _, entry := range hook.AllEntries() {
if entry.Data["type"] == "couper_backend" {
return entry.Data["status"].(int), entry.Data["validation"].([]string)[0]
}
}

return -1, ""
}

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

Expand Down
27 changes: 27 additions & 0 deletions server/testdata/integration/error_handler/06_couper.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
server {
api {
endpoint "/anything" {
proxy {
backend {
origin = "${env.COUPER_TEST_BACKEND_ADDR}"

openapi {
file = "02_schema.yaml"
}
}
}
}

error_handler "backend_openapi_validation" {
response {
status = 418
json_body = {
req_path = backend_requests.default.path
resp_status = backend_responses.default.status
resp_json_body_query = backend_responses.default.json_body.Query
resp_ct = backend_responses.default.headers.content-type
}
}
}
}
}

0 comments on commit d7dfacf

Please sign in to comment.