mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-10-09 01:33:26 +00:00
* Add OAS contract monitoring support * Pass the contract failure reason to UI * Fix the issues related to contract validation * Fix rest of the issues in the UI * Add documentation related to contract monitoring feature * Fix a typo in the docs * Unmarshal to `HTTPRequestResponsePair` only if the OAS validation is enabled * Fix an issue caused by the merge commit * Slightly change the logic in the `validateOAS` method Change the `contractText` value to `No Breaches` or `Breach` and make the text `white-space: nowrap`. * Retrieve and display the failure reason for both request and response Also display the content of the contract/OAS file in the UI. * Display the OAS under `CONTRACT` tab with syntax highlighting Also fix the styling in the entry feed. * Remove `EnforcePolicyFileDeprecated` constant * Log the other errors as well * Get context from caller instead * Define a type for the contract status and make its values enum-like * Remove an unnecessary `if` statement * Validate OAS in the CLI before passing it to Agent * Get rid of the `github.com/ghodss/yaml` dependency in `loadOAS` by using `LoadFromData` * Fix an artifact from the merge conflict
111 lines
2.7 KiB
Go
111 lines
2.7 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
|
|
"github.com/getkin/kin-openapi/openapi3"
|
|
"github.com/getkin/kin-openapi/openapi3filter"
|
|
"github.com/getkin/kin-openapi/routers"
|
|
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
|
|
"github.com/romana/rlog"
|
|
|
|
"github.com/up9inc/mizu/shared"
|
|
"github.com/up9inc/mizu/tap/api"
|
|
)
|
|
|
|
const (
|
|
ContractNotApplicable api.ContractStatus = 0
|
|
ContractPassed api.ContractStatus = 1
|
|
ContractFailed api.ContractStatus = 2
|
|
)
|
|
|
|
func loadOAS(ctx context.Context) (doc *openapi3.T, contractContent string, router routers.Router, err error) {
|
|
path := fmt.Sprintf("%s/%s", shared.RulePolicyPath, shared.ContractFileName)
|
|
bytes, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
rlog.Error(err.Error())
|
|
return
|
|
}
|
|
contractContent = string(bytes)
|
|
loader := &openapi3.Loader{Context: ctx}
|
|
doc, _ = loader.LoadFromData(bytes)
|
|
err = doc.Validate(ctx)
|
|
if err != nil {
|
|
rlog.Error(err.Error())
|
|
return
|
|
}
|
|
router, _ = legacyrouter.NewRouter(doc)
|
|
return
|
|
}
|
|
|
|
func validateOAS(ctx context.Context, doc *openapi3.T, router routers.Router, req *http.Request, res *http.Response) (isValid bool, reqErr error, resErr error) {
|
|
isValid = true
|
|
reqErr = nil
|
|
resErr = nil
|
|
|
|
// Find route
|
|
route, pathParams, err := router.FindRoute(req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Validate request
|
|
requestValidationInput := &openapi3filter.RequestValidationInput{
|
|
Request: req,
|
|
PathParams: pathParams,
|
|
Route: route,
|
|
}
|
|
if reqErr = openapi3filter.ValidateRequest(ctx, requestValidationInput); reqErr != nil {
|
|
isValid = false
|
|
}
|
|
|
|
responseValidationInput := &openapi3filter.ResponseValidationInput{
|
|
RequestValidationInput: requestValidationInput,
|
|
Status: res.StatusCode,
|
|
Header: res.Header,
|
|
}
|
|
|
|
if res.Body != nil {
|
|
body, _ := ioutil.ReadAll(res.Body)
|
|
res.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
|
responseValidationInput.SetBodyBytes(body)
|
|
}
|
|
|
|
// Validate response.
|
|
if resErr = openapi3filter.ValidateResponse(ctx, responseValidationInput); resErr != nil {
|
|
isValid = false
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func handleOAS(ctx context.Context, doc *openapi3.T, router routers.Router, req *http.Request, res *http.Response, contractContent string) (contract api.Contract) {
|
|
contract = api.Contract{
|
|
Content: contractContent,
|
|
Status: ContractNotApplicable,
|
|
}
|
|
|
|
isValid, reqErr, resErr := validateOAS(ctx, doc, router, req, res)
|
|
if isValid {
|
|
contract.Status = ContractPassed
|
|
} else {
|
|
contract.Status = ContractFailed
|
|
if reqErr != nil {
|
|
contract.RequestReason = reqErr.Error()
|
|
} else {
|
|
contract.RequestReason = ""
|
|
}
|
|
if resErr != nil {
|
|
contract.ResponseReason = resErr.Error()
|
|
} else {
|
|
contract.ResponseReason = ""
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|