Skip to content

Commit

Permalink
openapi block with properties instead of properties only; request/res…
Browse files Browse the repository at this point in the history
…ponse are always validated if openapi block is present; request is rejected if invalid and ignore_request_violations is not true; response is rejected if invalid and ignore_response_violations is not true (#21)
  • Loading branch information
Johannes Koch committed Oct 8, 2020
1 parent 6a1e7c3 commit 384bdd0
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 43 deletions.
16 changes: 3 additions & 13 deletions config/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ type Backend struct {
Path string `hcl:"path,optional"`
Timeout string `hcl:"timeout,optional"`
TTFBTimeout string `hcl:"ttfb_timeout,optional"`
OpenAPIFile string `hcl:"openapi_file,optional"`
ValidateReq bool `hcl:"validate_request,optional"`
ValidateRes bool `hcl:"validate_response,optional"`
OpenAPI *OpenAPI `hcl:"openapi,block"`
}

// Merge overrides the left backend configuration and returns a new instance.
Expand Down Expand Up @@ -65,16 +63,8 @@ func (b *Backend) Merge(other *Backend) (*Backend, []hcl.Body) {
result.TTFBTimeout = other.TTFBTimeout
}

if other.OpenAPIFile != "" {
result.OpenAPIFile = other.OpenAPIFile
}

if other.ValidateReq {
result.ValidateReq = other.ValidateReq
}

if other.ValidateRes {
result.ValidateRes = other.ValidateRes
if other.OpenAPI != nil {
result.OpenAPI = other.OpenAPI
}

return &result, bodies
Expand Down
7 changes: 7 additions & 0 deletions config/openapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package config

type OpenAPI struct {
File string `hcl:"file"`
IgnoreRequestViolations bool `hcl:"ignore_request_violations,optional"`
IgnoreResponseViolations bool `hcl:"ignore_response_violations,optional"`
}
16 changes: 4 additions & 12 deletions config/runtime/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ func BuildEntrypointHandlers(conf *config.Gateway, httpConf *HTTPConfig, log *lo
Path: beConf.Path,
Timeout: t,
TTFBTimeout: ttfbt,
OpenAPIFile: beConf.OpenAPIFile,
ValidateReq: beConf.ValidateReq,
ValidateRes: beConf.ValidateRes,
OpenAPI: handler.NewOpenAPIOptions(beConf.OpenAPI),
}, log, conf.Context)
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -196,9 +194,7 @@ func BuildEntrypointHandlers(conf *config.Gateway, httpConf *HTTPConfig, log *lo
Path: beConf.Path,
Timeout: t,
TTFBTimeout: ttfbt,
OpenAPIFile: beConf.OpenAPIFile,
ValidateReq: beConf.ValidateReq,
ValidateRes: beConf.ValidateRes,
OpenAPI: handler.NewOpenAPIOptions(beConf.OpenAPI),
}, log, conf.Context)
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -268,9 +264,7 @@ func BuildEntrypointHandlers(conf *config.Gateway, httpConf *HTTPConfig, log *lo
Path: beConf.Path,
Timeout: t,
TTFBTimeout: ttfbt,
OpenAPIFile: beConf.OpenAPIFile,
ValidateReq: beConf.ValidateReq,
ValidateRes: beConf.ValidateRes,
OpenAPI: handler.NewOpenAPIOptions(beConf.OpenAPI),
}, log, conf.Context)
if err != nil {
log.Fatal(err)
Expand Down Expand Up @@ -488,9 +482,7 @@ func newInlineBackend(evalCtx *hcl.EvalContext, inlineDef hcl.Body, cors *config
Path: beConf.Path,
Timeout: t,
TTFBTimeout: ttfbt,
OpenAPIFile: beConf.OpenAPIFile,
ValidateReq: beConf.ValidateReq,
ValidateRes: beConf.ValidateRes,
OpenAPI: handler.NewOpenAPIOptions(beConf.OpenAPI),
}, log, evalCtx)
return proxy, beConf, err
}
Expand Down
8 changes: 5 additions & 3 deletions couper.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ server "couperConnect" {
endpoint "/httpbin/**" {
backend "httpbin" {
path = "/**"
openapi_file = "./upstream/httpbin.yaml"
validate_request = true
validate_response = true
openapi {
file = "./upstream/httpbin.yaml"
# ignore_request_violations = true
# ignore_response_violations = true
}

request_headers = {
x-env-user = ["override-user"]
Expand Down
50 changes: 35 additions & 15 deletions handler/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ type ProxyOptions struct {
ConnectTimeout, Timeout, TTFBTimeout time.Duration
Context []hcl.Body
BackendName string
Hostname, Origin, Path, OpenAPIFile string
ValidateReq, ValidateRes bool
// Hostname, Origin, Path, OpenAPIFile string
Hostname, Origin, Path string
// ValidateReq, ValidateRes bool
CORS *CORSOptions
OpenAPI *OpenAPIOptions
}

type CORSOptions struct {
Expand Down Expand Up @@ -102,6 +104,23 @@ func (c *CORSOptions) AllowsOrigin(origin string) bool {
return false
}

type OpenAPIOptions struct {
File string
IgnoreRequestViolations bool
IgnoreResponseViolations bool
}

func NewOpenAPIOptions(openapi *config.OpenAPI) *OpenAPIOptions {
if openapi == nil {
return nil
}
return &OpenAPIOptions{
File: openapi.File,
IgnoreRequestViolations: openapi.IgnoreRequestViolations,
IgnoreResponseViolations: openapi.IgnoreResponseViolations,
}
}

func NewProxy(options *ProxyOptions, log *logrus.Entry, evalCtx *hcl.EvalContext) (http.Handler, error) {
if options.Origin == "" {
return nil, OriginRequiredError
Expand Down Expand Up @@ -161,12 +180,12 @@ func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}

func (p *Proxy) preparetRequestValidatation(outreq *http.Request) (context.Context, *openapi3filter.Route, *openapi3filter.RequestValidationInput, error) {
if p.options.ValidateReq || p.options.ValidateRes {
if p.options.OpenAPI != nil {
dir, err := os.Getwd()
if err != nil {
return nil, nil, nil, err
}
router := openapi3filter.NewRouter().WithSwaggerFromFile(dir + "/" + p.options.OpenAPIFile)
router := openapi3filter.NewRouter().WithSwaggerFromFile(dir + "/" + p.options.OpenAPI.File)
validationCtx := context.Background()
route, pathParams, _ := router.FindRoute(outreq.Method, outreq.URL)

Expand All @@ -181,7 +200,7 @@ func (p *Proxy) preparetRequestValidatation(outreq *http.Request) (context.Conte
}

func (p *Proxy) prepareResponseValidatation(requestValidationInput *openapi3filter.RequestValidationInput, res *http.Response) (*openapi3filter.ResponseValidationInput, []byte, error) {
if p.options.ValidateRes {
if p.options.OpenAPI != nil {
responseValidationInput := &openapi3filter.ResponseValidationInput{
RequestValidationInput: requestValidationInput,
Status: res.StatusCode,
Expand Down Expand Up @@ -253,12 +272,14 @@ func (p *Proxy) roundtrip(rw http.ResponseWriter, req *http.Request) {
couperErr.DefaultJSON.ServeError(couperErr.UpstreamRequestValidationFailed).ServeHTTP(rw, req)
return
}
if (p.options.ValidateReq) {
if requestValidationInput != nil {
if err := openapi3filter.ValidateRequest(validationCtx, requestValidationInput); err != nil {
// TODO: use error template from parent endpoint>api>server
p.log.WithField("upstream request validation", err).Error()
couperErr.DefaultJSON.ServeError(couperErr.UpstreamRequestValidationFailed).ServeHTTP(rw, req)
return
if !p.options.OpenAPI.IgnoreRequestViolations {
couperErr.DefaultJSON.ServeError(couperErr.UpstreamRequestValidationFailed).ServeHTTP(rw, req)
return
}
}
}

Expand All @@ -273,21 +294,20 @@ func (p *Proxy) roundtrip(rw http.ResponseWriter, req *http.Request) {

responseValidationInput, body, err := p.prepareResponseValidatation(requestValidationInput, res)
if err != nil {
// this only happens if response body buffering fails
// TODO: use error template from parent endpoint>api>server
p.log.WithField("upstream response validation", err).Error()
couperErr.DefaultJSON.ServeError(couperErr.UpstreamResponseBufferingFailed).ServeHTTP(rw, req)
return
}
if responseValidationInput != nil {
if route != nil {
if err := openapi3filter.ValidateResponse(validationCtx, responseValidationInput); err != nil {
// TODO: use error template from parent endpoint>api>server
p.log.WithField("upstream response validation", err).Error()
if responseValidationInput != nil && route != nil {
if err := openapi3filter.ValidateResponse(validationCtx, responseValidationInput); err != nil {
// TODO: use error template from parent endpoint>api>server
p.log.WithField("upstream response validation", err).Error()
if !p.options.OpenAPI.IgnoreResponseViolations {
couperErr.DefaultJSON.ServeError(couperErr.UpstreamResponseValidationFailed).ServeHTTP(rw, req)
return
}
} else {
p.log.Info("response validation enabled, but no route found")
}
}

Expand Down

0 comments on commit 384bdd0

Please sign in to comment.