diff --git a/api/internal/handler/route_online_debug/route_online_debug.go b/api/internal/handler/route_online_debug/route_online_debug.go new file mode 100644 index 0000000000..f3b9d43959 --- /dev/null +++ b/api/internal/handler/route_online_debug/route_online_debug.go @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package route_online_debug + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strings" + "time" + + "github.com/apisix/manager-api/internal/handler" + "github.com/gin-gonic/gin" + "github.com/shiningrush/droplet" + "github.com/shiningrush/droplet/data" + "github.com/shiningrush/droplet/wrapper" + wgin "github.com/shiningrush/droplet/wrapper/gin" +) + +type Handler struct { +} + +func NewHandler() (handler.RouteRegister, error) { + return &Handler{}, nil +} + +type ProtocolSupport interface { + RequestForwarding(c droplet.Context) (interface{}, error) +} + +var protocolMap map[string]ProtocolSupport + +func init() { + protocolMap = make(map[string]ProtocolSupport) + protocolMap["http"] = &HTTPProtocolSupport{} +} + +func (h *Handler) ApplyRoute(r *gin.Engine) { + r.POST("/apisix/admin/debug-request-forwarding", wgin.Wraps(DebugRequestForwarding, + wrapper.InputType(reflect.TypeOf(ParamsInput{})))) +} + +type ParamsInput struct { + URL string `json:"url,omitempty"` + RequestProtocol string `json:"request_protocol,omitempty"` + BodyParams map[string]string `json:"body_params,omitempty"` + Method string `json:"method,omitempty"` + HeaderParams map[string][]string `json:"header_params,omitempty"` +} + +type Result struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Data interface{} `json:"data,omitempty"` +} + +func DebugRequestForwarding(c droplet.Context) (interface{}, error) { + //TODO: other Protocols, e.g: grpc, websocket + paramsInput := c.Input().(*ParamsInput) + requestProtocol := paramsInput.RequestProtocol + if requestProtocol == "" { + requestProtocol = "http" + } + if v, ok := protocolMap[requestProtocol]; ok { + return v.RequestForwarding(c) + } else { + return &data.SpecCodeResponse{StatusCode: http.StatusBadRequest}, fmt.Errorf("protocol unspported %s", paramsInput.RequestProtocol) + } +} + +type HTTPProtocolSupport struct { +} + +func (h *HTTPProtocolSupport) RequestForwarding(c droplet.Context) (interface{}, error) { + paramsInput := c.Input().(*ParamsInput) + bodyParams, _ := json.Marshal(paramsInput.BodyParams) + client := &http.Client{} + + client.Timeout = 5 * time.Second + req, err := http.NewRequest(strings.ToUpper(paramsInput.Method), paramsInput.URL, strings.NewReader(string(bodyParams))) + if err != nil { + return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err + } + for k, v := range paramsInput.HeaderParams { + for _, v1 := range v { + req.Header.Add(k, v1) + } + } + resp, err := client.Do(req) + if err != nil { + return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err + } + returnData := make(map[string]interface{}) + result := &Result{} + err = json.Unmarshal(body, &returnData) + if err != nil { + result.Code = resp.StatusCode + result.Message = resp.Status + result.Data = string(body) + } else { + result.Code = resp.StatusCode + result.Message = resp.Status + result.Data = returnData + + } + return result, nil +} diff --git a/api/internal/route.go b/api/internal/route.go index 06c4271174..801f369071 100644 --- a/api/internal/route.go +++ b/api/internal/route.go @@ -34,6 +34,7 @@ import ( "github.com/apisix/manager-api/internal/handler/healthz" "github.com/apisix/manager-api/internal/handler/plugin" "github.com/apisix/manager-api/internal/handler/route" + "github.com/apisix/manager-api/internal/handler/route_online_debug" "github.com/apisix/manager-api/internal/handler/server_info" "github.com/apisix/manager-api/internal/handler/service" "github.com/apisix/manager-api/internal/handler/ssl" @@ -66,6 +67,7 @@ func SetUpRouter() *gin.Engine { plugin.NewHandler, healthz.NewHandler, authentication.NewHandler, + route_online_debug.NewHandler, server_info.NewHandler, } diff --git a/api/test/e2e/base.go b/api/test/e2e/base.go index 8f4116acb3..99c763d259 100644 --- a/api/test/e2e/base.go +++ b/api/test/e2e/base.go @@ -31,6 +31,7 @@ import ( ) var token string +var APISIXInternalUrl = "http://172.16.238.30:9080" func init() { //login to get auth token diff --git a/api/test/e2e/route_online_debug_test.go b/api/test/e2e/route_online_debug_test.go new file mode 100644 index 0000000000..4dd88f2a50 --- /dev/null +++ b/api/test/e2e/route_online_debug_test.go @@ -0,0 +1,852 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package e2e + +import ( + "io/ioutil" + "net/http" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" +) + +func TestRoute_Online_Debug_Route_Not_Exist(t *testing.T) { + tests := []HttpTestCase{ + { + caseDesc: "hit route that not exist", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello_", + ExpectStatus: http.StatusNotFound, + ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", + }, + } + for _, tc := range tests { + testCaseCheck(tc) + } + basepath := "http://127.0.0.1:9000/apisix/admin/debug-request-forwarding" + request, _ := http.NewRequest("POST", basepath, strings.NewReader(`{"url": "`+APISIXInternalUrl+`/hello_","method": "GET","request_protocol": "http"}`)) + request.Header.Add("Authorization", token) + resp, err := http.DefaultClient.Do(request) + if err != nil { + return + } + defer resp.Body.Close() + respBody, _ := ioutil.ReadAll(resp.Body) + realBody := gjson.Get(string(respBody), "data") + assert.Equal(t, `{"code":404,"message":"404 Not Found","data":{"error_msg":"404 Route Not Found"}}`, realBody.String()) +} + +func TestRoute_Online_Debug_Route_With_Query_Params(t *testing.T) { + tests := []HttpTestCase{ + { + caseDesc: "hit route that not exist", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: "{\"error_msg\":\"404 Route Not Found\"}\n", + }, + { + caseDesc: "create route with query params", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "methods": ["GET"], + "vars": [ + ["arg_name","==","aaa"] + ], + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + Sleep: sleepTime, + }, + { + caseDesc: "online debug route with query params", + Object: ManagerApiExpect(t), + Method: http.MethodPost, + Path: "/apisix/admin/debug-request-forwarding", + Body: `{ + "url": "` + APISIXInternalUrl + `/hello?name=aaa", + "request_protocol": "http", + "method": "GET" + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify the deleted route ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } +} + +func TestRoute_Online_Debug_Route_With_Header_Params(t *testing.T) { + tests := []HttpTestCase{ + { + caseDesc: "make sure the route is not created ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + { + caseDesc: "create route with header params", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "methods": ["GET"], + "vars": [ + ["http_version","==","v2"] + ], + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + Sleep: sleepTime, + }, + { + caseDesc: "online debug route with header params", + Object: ManagerApiExpect(t), + Method: http.MethodPost, + Path: "/apisix/admin/debug-request-forwarding", + Body: `{ + "url": "` + APISIXInternalUrl + `/hello", + "request_protocol": "http", + "method": "GET", + "header_params": { + "version": ["v2"] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify the deleted route ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } +} + +func TestRoute_Online_Debug_Route_With_Body_Params(t *testing.T) { + tests := []HttpTestCase{ + { + caseDesc: "make sure the route is not created ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + { + caseDesc: "create route with method POST", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "methods": ["POST"], + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + Sleep: sleepTime, + }, + { + caseDesc: "online debug route with body params", + Object: ManagerApiExpect(t), + Method: http.MethodPost, + Path: "/apisix/admin/debug-request-forwarding", + Body: `{ + "url": "` + APISIXInternalUrl + `/hello", + "request_protocol": "http", + "method": "POST", + "body_params": { + "name": "test", + "desc": "online debug route with body params" + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify the deleted route ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } +} + +func TestRoute_Online_Debug_Route_With_Basic_Auth(t *testing.T) { + tests := []HttpTestCase{ + { + caseDesc: "make sure the route is not created ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + { + caseDesc: "create route enable basic-auth plugin", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "methods": ["GET"], + "plugins": { + "basic-auth": {} + }, + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + Sleep: sleepTime, + }, + { + caseDesc: "make sure the consumer is not created", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Path: "/apisix/admin/consumers/jack", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusNotFound, + }, + { + caseDesc: "create consumer", + Object: ManagerApiExpect(t), + Path: "/apisix/admin/consumers", + Method: http.MethodPost, + Body: `{ + "username": "jack", + "plugins": { + "basic-auth": { + "disable": false, + "username": "jack", + "password": "123456" + } + }, + "desc": "test description" + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + Sleep: sleepTime, + }, + { + caseDesc: "online debug route with username and password", + Object: ManagerApiExpect(t), + Method: http.MethodPost, + Path: "/apisix/admin/debug-request-forwarding", + Body: `{ + "url": "` + APISIXInternalUrl + `/hello", + "request_protocol": "http", + "method": "GET", + "header_params": { + "Authorization": ["Basic amFjazoxMjM0NTYKIA=="] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } + + // online debug without basic-auth + basepath := "http://127.0.0.1:9000/apisix/admin/debug-request-forwarding" + request, _ := http.NewRequest("POST", basepath, strings.NewReader(`{"url": "`+APISIXInternalUrl+`/hello","method": "GET","request_protocol":"http"}`)) + request.Header.Add("Authorization", token) + resp, err := http.DefaultClient.Do(request) + if err != nil { + return + } + defer resp.Body.Close() + respBody, _ := ioutil.ReadAll(resp.Body) + realBody := gjson.Get(string(respBody), "data") + assert.Equal(t, `{"code":401,"message":"401 Unauthorized","data":{"message":"Missing authorization in request"}}`, realBody.String()) + + // clear test data + tests = []HttpTestCase{ + { + caseDesc: "delete consumer", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/consumers/jack", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify route with the jwt token from just deleted consumer", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + Headers: map[string]string{"Authorization": "Basic amFjazoxMjM0NTYKIA=="}, + ExpectStatus: http.StatusUnauthorized, + ExpectBody: `{"message":"Missing related consumer"}`, + Sleep: sleepTime, + }, + { + caseDesc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify the deleted route ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } +} + +func TestRoute_Online_Debug_Route_With_Jwt_Auth(t *testing.T) { + tests := []HttpTestCase{ + { + caseDesc: "make sure the route is not created ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + { + caseDesc: "create route enable jwt-auth plugin", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "methods": ["GET"], + "plugins": { + "jwt-auth": {} + }, + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + Sleep: sleepTime, + }, + { + caseDesc: "make sure the consumer is not created", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Path: "/apisix/admin/consumers/jack", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusNotFound, + Sleep: sleepTime, + }, + { + caseDesc: "create consumer", + Object: ManagerApiExpect(t), + Path: "/apisix/admin/consumers", + Method: http.MethodPut, + Body: `{ + "username": "jack", + "plugins": { + "jwt-auth": { + "key": "user-key", + "secret": "my-secret-key", + "algorithm": "HS256" + } + }, + "desc": "test description" + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } + + // sign jwt token + body, status, err := httpGet("http://127.0.0.1:9080/apisix/plugin/jwt/sign?key=user-key") + assert.Nil(t, err) + assert.Equal(t, http.StatusOK, status) + jwtToken := string(body) + + time.Sleep(sleepTime) + + tests = []HttpTestCase{ + { + caseDesc: "online debug route with jwt token", + Object: ManagerApiExpect(t), + Method: http.MethodPost, + Path: "/apisix/admin/debug-request-forwarding", + Body: `{ + "url": "` + APISIXInternalUrl + `/hello", + "request_protocol": "http", + "method": "GET", + "header_params": { + "Authorization": ["` + jwtToken + `"] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } + + // online debug without jwt-auth + basepath := "http://127.0.0.1:9000/apisix/admin/debug-request-forwarding" + request, _ := http.NewRequest("POST", basepath, strings.NewReader(`{"url": "`+APISIXInternalUrl+`/hello","method": "GET","request_protocol":"http"}`)) + request.Header.Add("Authorization", token) + resp, err := http.DefaultClient.Do(request) + if err != nil { + return + } + defer resp.Body.Close() + respBody, _ := ioutil.ReadAll(resp.Body) + realBody := gjson.Get(string(respBody), "data") + assert.Equal(t, `{"code":401,"message":"401 Unauthorized","data":{"message":"Missing JWT token in request"}}`, realBody.String()) + + // clear test data + tests = []HttpTestCase{ + { + caseDesc: "delete consumer", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/consumers/jack", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify route with the jwt token from just deleted consumer", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + Headers: map[string]string{"Authorization": jwtToken}, + ExpectStatus: http.StatusUnauthorized, + ExpectBody: `{"message":"Missing related consumer"}`, + Sleep: sleepTime, + }, + { + caseDesc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify the deleted route ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } +} + +func TestRoute_Online_Debug_Route_With_Key_Auth(t *testing.T) { + tests := []HttpTestCase{ + { + caseDesc: "make sure the route is not created ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + }, + { + caseDesc: "create route enable key-auth plugin", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "methods": ["GET"], + "plugins": { + "key-auth": {} + }, + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "make sure the consumer is not created", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Path: "/apisix/admin/consumers/jack", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusNotFound, + }, + { + caseDesc: "create consumer", + Object: ManagerApiExpect(t), + Path: "/apisix/admin/consumers", + Method: http.MethodPut, + Body: `{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "user-key" + } + }, + "desc": "test description" + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "online debug route with apikey", + Object: ManagerApiExpect(t), + Method: http.MethodPost, + Path: "/apisix/admin/debug-request-forwarding", + Body: `{ + "url": "` + APISIXInternalUrl + `/hello", + "request_protocol": "http", + "method": "GET", + "header_params": { + "apikey": ["user-key"] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } + + // online debug without key-auth + basepath := "http://127.0.0.1:9000/apisix/admin/debug-request-forwarding" + request, _ := http.NewRequest("POST", basepath, strings.NewReader(`{"url": "`+APISIXInternalUrl+`/hello","method": "GET","request_protocol": "http"}`)) + request.Header.Add("Authorization", token) + resp, err := http.DefaultClient.Do(request) + if err != nil { + return + } + defer resp.Body.Close() + respBody, _ := ioutil.ReadAll(resp.Body) + realBody := gjson.Get(string(respBody), "data") + assert.Equal(t, `{"code":401,"message":"401 Unauthorized","data":{"message":"Missing API key found in request"}}`, realBody.String()) + + // clear test data + tests = []HttpTestCase{ + { + caseDesc: "delete consumer", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/consumers/jack", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify route with the jwt token from just deleted consumer", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + Headers: map[string]string{"apikey": "user-key"}, + ExpectStatus: http.StatusUnauthorized, + ExpectBody: `{"message":"Missing related consumer"}`, + Sleep: sleepTime, + }, + { + caseDesc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify the deleted route ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } +} + +func TestRoute_Online_Debug_Route_With_Query_Params_Key_Auth(t *testing.T) { + tests := []HttpTestCase{ + { + caseDesc: "make sure the route is not created ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + }, + { + caseDesc: "create route enable key-auth plugin", + Object: ManagerApiExpect(t), + Method: http.MethodPut, + Path: "/apisix/admin/routes/r1", + Body: `{ + "uri": "/hello", + "methods": ["GET"], + "vars": [ + ["arg_name","==","aaa"] + ], + "plugins": { + "key-auth": {} + }, + "upstream": { + "type": "roundrobin", + "nodes": [{ + "host": "172.16.238.20", + "port": 1980, + "weight": 1 + }] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "make sure the consumer is not created", + Object: ManagerApiExpect(t), + Method: http.MethodGet, + Path: "/apisix/admin/consumers/jack", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusNotFound, + }, + { + caseDesc: "create consumer", + Object: ManagerApiExpect(t), + Path: "/apisix/admin/consumers", + Method: http.MethodPut, + Body: `{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "user-key" + } + }, + "desc": "test description" + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "online debug route with apikey", + Object: ManagerApiExpect(t), + Method: http.MethodPost, + Path: "/apisix/admin/debug-request-forwarding", + Body: `{ + "url": "` + APISIXInternalUrl + `/hello?name=aaa", + "request_protocol": "http", + "method": "GET", + "header_params": { + "apikey": ["user-key"] + } + }`, + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } + + // online debug without key-auth + basepath := "http://127.0.0.1:9000/apisix/admin/debug-request-forwarding" + request, _ := http.NewRequest("POST", basepath, strings.NewReader(`{"url": "`+APISIXInternalUrl+`/hello?name=aaa","method": "GET","request_protocol": "http"}`)) + request.Header.Add("Authorization", token) + resp, err := http.DefaultClient.Do(request) + if err != nil { + return + } + defer resp.Body.Close() + respBody, _ := ioutil.ReadAll(resp.Body) + realBody := gjson.Get(string(respBody), "data") + assert.Equal(t, `{"code":401,"message":"401 Unauthorized","data":{"message":"Missing API key found in request"}}`, realBody.String()) + + // clear test data + tests = []HttpTestCase{ + { + caseDesc: "delete consumer", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/consumers/jack", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify route with the jwt token from just deleted consumer", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: `/hello`, + Query: "name=aaa", + Headers: map[string]string{"apikey": "user-key"}, + ExpectStatus: http.StatusUnauthorized, + ExpectBody: `{"message":"Missing related consumer"}`, + Sleep: sleepTime, + }, + { + caseDesc: "delete route", + Object: ManagerApiExpect(t), + Method: http.MethodDelete, + Path: "/apisix/admin/routes/r1", + Headers: map[string]string{"Authorization": token}, + ExpectStatus: http.StatusOK, + }, + { + caseDesc: "verify the deleted route ", + Object: APISIXExpect(t), + Method: http.MethodGet, + Path: "/hello", + ExpectStatus: http.StatusNotFound, + ExpectBody: `{"error_msg":"404 Route Not Found"}`, + Sleep: sleepTime, + }, + } + + for _, tc := range tests { + testCaseCheck(tc) + } +}