Skip to content

Commit

Permalink
added a counter metric for errors
Browse files Browse the repository at this point in the history
Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>
  • Loading branch information
rudrakhp committed Nov 1, 2023
1 parent 119fc3f commit da9e765
Show file tree
Hide file tree
Showing 3 changed files with 324 additions and 23 deletions.
62 changes: 62 additions & 0 deletions internal/error/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package internal

import (
"errors"
"fmt"
)

// Error is the error type returned by the internal check function
type Error struct {
Code string `json:"code"`
err error `json:"-"`
}

const (

// StartCheckErr error code returned when unable to start new execution
StartCheckErr string = "start_check_error"

// StartTxnErr error code returned when unable to start new storage transaction
StartTxnErr string = "start_txn_error"

// RequestParseErr error code returned when unable to parse protobuf request to input map
RequestParseErr string = "request_parse_error"

// CheckRequestTimeoutErr error code returned when context deadline exceeds before eval
CheckRequestTimeoutErr string = "check_request_timeout"

// InputParseErr error code returned when unable to convert input map to ast value
InputParseErr string = "input_parse_error"

// EnvoyAuthEvalErr error code returned when auth eval fails
EnvoyAuthEvalErr string = "envoyauth_eval_error"

// EnvoyAuthResultErr error code returned when error in result from auth eval
EnvoyAuthResultErr string = "envoyauth_result_error"
)

// Is allows matching internal errors using errors.Is
func (e *Error) Is(target error) bool {
var t *Error
if errors.As(target, &t) {
return (t.Code == "" || e.Code == t.Code) && errors.Is(e.Unwrap(), t.Unwrap())
}
return false
}

// Error allows converting internal Error to string type
func (e *Error) Error() string {
msg := fmt.Sprintf("%v: %v", e.Code, e.Unwrap().Error())
return msg
}

// Wrap wraps error as an internal error
func (e *Error) Wrap(err error) *Error {
e.err = err
return e
}

// Unwrap gets error wrapped in the internal error
func (e *Error) Unwrap() error {
return e.err
}
85 changes: 70 additions & 15 deletions internal/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ package internal
import (
"context"
"fmt"
internal "github.com/open-policy-agent/opa-envoy-plugin/internal/error"
"github.com/open-policy-agent/opa/topdown"
"math"
"net"
"net/url"
Expand Down Expand Up @@ -181,7 +183,13 @@ func New(m *plugins.Manager, cfg *Config) plugins.Plugin {
},
}, []string{"handler"})
plugin.metricAuthzDuration = *histogramAuthzDuration
errorCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "error_counter",
Help: "A counter for errors",
}, []string{"reason"})
plugin.metricErrorCounter = *errorCounter
plugin.manager.PrometheusRegister().MustRegister(histogramAuthzDuration)
plugin.manager.PrometheusRegister().MustRegister(errorCounter)
}

m.UpdatePluginStatus(PluginName, &plugins.Status{State: plugins.StateNotReady})
Expand Down Expand Up @@ -214,6 +222,7 @@ type envoyExtAuthzGrpcServer struct {
interQueryBuiltinCache iCache.InterQueryCache
distributedTracingOpts tracing.Options
metricAuthzDuration prometheus.HistogramVec
metricErrorCounter prometheus.CounterVec
}

type envoyExtAuthzV2Wrapper struct {
Expand Down Expand Up @@ -345,25 +354,34 @@ func (p *envoyExtAuthzGrpcServer) Check(ctx context.Context, req *ext_authz_v3.C
if code := stop(); resp != nil && code != nil {
resp.Status = code
}
return resp, err

if err != nil {
return resp, err.Unwrap()
}
return resp, nil
}

func (p *envoyExtAuthzGrpcServer) check(ctx context.Context, req interface{}) (*ext_authz_v3.CheckResponse, func() *rpc_status.Status, error) {
func (p *envoyExtAuthzGrpcServer) check(ctx context.Context, req interface{}) (*ext_authz_v3.CheckResponse, func() *rpc_status.Status, *internal.Error) {
var err error
var evalErr error
var internalErr internal.Error
start := time.Now()
logger := p.manager.Logger()

result, stopeval, err := envoyauth.NewEvalResult()
if err != nil {
logger.WithFields(map[string]interface{}{"err": err}).Error("Unable to start new evaluation.")
return nil, func() *rpc_status.Status { return nil }, err
internalErr = internal.Error{Code: internal.StartCheckErr}
internalErr.Wrap(err)
return nil, func() *rpc_status.Status { return nil }, &internalErr
}

txn, txnClose, err := result.GetTxn(ctx, p.Store())
if err != nil {
logger.WithFields(map[string]interface{}{"err": err}).Error("Unable to start new storage transaction.")
return nil, func() *rpc_status.Status { return nil }, err
internalErr = internal.Error{Code: internal.StartTxnErr}
internalErr.Wrap(err)
return nil, func() *rpc_status.Status { return nil }, &internalErr
}

result.Txn = txn
Expand All @@ -374,9 +392,20 @@ func (p *envoyExtAuthzGrpcServer) check(ctx context.Context, req interface{}) (*

stop := func() *rpc_status.Status {
stopeval()
if internalErr.Unwrap() != nil || internalErr.Code != "" {
var topdownError *topdown.Error
if errors.As(internalErr.Unwrap(), &topdownError) {
p.metricErrorCounter.With(prometheus.Labels{"reason": topdownError.Code}).Inc()
} else if internalErr.Code != "" {
p.metricErrorCounter.With(prometheus.Labels{"reason": internalErr.Code}).Inc()
} else {
p.metricErrorCounter.With(prometheus.Labels{"reason": "unknown_check_error"}).Inc()
}
}
logErr := p.log(ctx, input, result, err)
if logErr != nil {
_ = txnClose(ctx, logErr) // Ignore error
p.metricErrorCounter.With(prometheus.Labels{"reason": "unknown_log_error"}).Inc()
return &rpc_status.Status{
Code: int32(code.Code_UNKNOWN),
Message: logErr.Error(),
Expand All @@ -388,31 +417,42 @@ func (p *envoyExtAuthzGrpcServer) check(ctx context.Context, req interface{}) (*

input, err = envoyauth.RequestToInput(req, logger, p.cfg.protoSet, p.cfg.SkipRequestBodyParse)
if err != nil {
return nil, stop, err
internalErr = internal.Error{Code: internal.RequestParseErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

if ctx.Err() != nil {
err = errors.Wrap(ctx.Err(), "check request timed out before query execution")
return nil, stop, err
internalErr = internal.Error{Code: internal.CheckRequestTimeoutErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

var inputValue ast.Value
inputValue, err = ast.InterfaceToValue(input)
if err != nil {
return nil, stop, err
internalErr = internal.Error{Code: internal.InputParseErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

if err = envoyauth.Eval(ctx, p, inputValue, result); err != nil {
evalErr = err
return nil, stop, err
internalErr = internal.Error{Code: internal.EnvoyAuthEvalErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

resp := &ext_authz_v3.CheckResponse{}

var allowed bool
allowed, err = result.IsAllowed()
if err != nil {
return nil, stop, errors.Wrap(err, "failed to get response status")
err = errors.Wrap(err, "failed to get response status")
internalErr = internal.Error{Code: internal.EnvoyAuthResultErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

status := int32(code.Code_PERMISSION_DENIED)
Expand All @@ -426,21 +466,30 @@ func (p *envoyExtAuthzGrpcServer) check(ctx context.Context, req interface{}) (*
var responseHeaders []*ext_core_v3.HeaderValueOption
responseHeaders, err = result.GetResponseEnvoyHeaderValueOptions()
if err != nil {
return nil, stop, errors.Wrap(err, "failed to get response headers")
err = errors.Wrap(err, "failed to get response headers")
internalErr = internal.Error{Code: internal.EnvoyAuthResultErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

if status == int32(code.Code_OK) {

var headersToRemove []string
headersToRemove, err = result.GetRequestHTTPHeadersToRemove()
if err != nil {
return nil, stop, errors.Wrap(err, "failed to get request headers to remove")
err = errors.Wrap(err, "failed to get request headers to remove")
internalErr = internal.Error{Code: internal.EnvoyAuthResultErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

var responseHeadersToAdd []*ext_core_v3.HeaderValueOption
responseHeadersToAdd, err = result.GetResponseHTTPHeadersToAdd()
if err != nil {
return nil, stop, errors.Wrap(err, "failed to get response headers to send to client")
err = errors.Wrap(err, "failed to get response headers to send to client")
internalErr = internal.Error{Code: internal.EnvoyAuthResultErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

resp.HttpResponse = &ext_authz_v3.CheckResponse_OkResponse{
Expand All @@ -454,13 +503,19 @@ func (p *envoyExtAuthzGrpcServer) check(ctx context.Context, req interface{}) (*
var body string
body, err = result.GetResponseBody()
if err != nil {
return nil, stop, errors.Wrap(err, "failed to get response body")
err = errors.Wrap(err, "failed to get response body")
internalErr = internal.Error{Code: internal.EnvoyAuthResultErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

var httpStatus *ext_type_v3.HttpStatus
httpStatus, err = result.GetResponseEnvoyHTTPStatus()
if err != nil {
return nil, stop, errors.Wrap(err, "failed to get response http status")
err = errors.Wrap(err, "failed to get response http status")
internalErr = internal.Error{Code: internal.EnvoyAuthResultErr}
internalErr.Wrap(err)
return nil, stop, &internalErr
}

deniedResponse := &ext_authz_v3.DeniedHttpResponse{
Expand Down Expand Up @@ -577,7 +632,7 @@ func (p *envoyExtAuthzV2Wrapper) Check(ctx context.Context, req *ext_authz_v2.Ch
}()

if err != nil {
return nil, err
return nil, err.Unwrap()
}
respV2 = v2Response(respV3)
return respV2, nil
Expand Down
Loading

0 comments on commit da9e765

Please sign in to comment.