diff --git a/pkg/api/api0/api.go b/pkg/api/api0/api.go index 2e3593c..c3f819b 100644 --- a/pkg/api/api0/api.go +++ b/pkg/api/api0/api.go @@ -14,6 +14,7 @@ package api0 import ( "bytes" + "context" "crypto/rand" "encoding/hex" "encoding/json" @@ -30,6 +31,7 @@ import ( "github.com/r2northstar/atlas/pkg/eax" "github.com/r2northstar/atlas/pkg/metricsx" "github.com/r2northstar/atlas/pkg/nspkt" + "github.com/r2northstar/atlas/pkg/nsrule" "github.com/r2northstar/atlas/pkg/origin" "github.com/rs/zerolog/hlog" "golang.org/x/mod/semver" @@ -110,6 +112,9 @@ type Handler struct { // empty region and no error if no region is to be assigned. GetRegion func(netip.Addr, ip2x.Record) (string, error) + // Rules provides a ruleset to apply. + Rules *nsrule.RuleSet + metricsInit sync.Once metricsObj apiMetrics @@ -127,6 +132,14 @@ type connectState struct { gotPdata atomic.Bool } +type rulesContextKey struct{} + +type rulesContextValue struct { + e nsrule.Env + s *nsrule.RuleSet + t nsrule.Tags +} + // ServeHTTP routes requests to Handler. func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var notPanicked bool // this lets us catch panics without swallowing them @@ -136,6 +149,15 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } }() + if h.Rules != nil { + r = r.WithContext(context.WithValue(r.Context(), rulesContextKey{}, &rulesContextValue{ + e: nsrule.NewEnv(), + s: h.Rules, + t: make(nsrule.Tags), + })) + h.EvalRules(r, nil) + } + w.Header().Set("Server", "Atlas") switch r.URL.Path { @@ -174,6 +196,27 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { notPanicked = true } +func (h *Handler) EvalRules(r *http.Request, update func(e nsrule.Env)) { + if v := r.Context().Value(rulesContextKey{}); v != nil { + t := time.Now() + v := v.(*rulesContextValue) + if update != nil { + update(v.e) + } + for _, err := range v.s.Evaluate(v.e, v.t) { + hlog.FromRequest(r).Warn().Err(err).Msg("failed to evaluate rule") + } + h.m().rule_evaluation_time_seconds.UpdateDuration(t) + } +} + +func (h *Handler) Tags(r *http.Request) nsrule.Tags { + if v := r.Context().Value(rulesContextKey{}); v != nil { + return v.(*rulesContextValue).t + } + return nsrule.Tags{} +} + // CheckLauncherVersion checks if the r was made by NorthstarLauncher and if it // is at least MinimumLauncherVersion. func (h *Handler) CheckLauncherVersion(r *http.Request, client bool) bool { diff --git a/pkg/api/api0/metrics.go b/pkg/api/api0/metrics.go index ac54944..9e15ee6 100644 --- a/pkg/api/api0/metrics.go +++ b/pkg/api/api0/metrics.go @@ -184,6 +184,7 @@ type apiMetrics struct { fail_other_error *metrics.Counter http_method_not_allowed *metrics.Counter } + rule_evaluation_time_seconds *metrics.Histogram } func (h *Handler) Metrics() *metrics.Set { @@ -474,6 +475,7 @@ func (h *Handler) m() *apiMetrics { mo.player_pdata_requests_total.fail_pdata_invalid = mo.set.NewCounter(`atlas_api0_player_pdata_requests_total{result="fail_pdata_invalid"}`) mo.player_pdata_requests_total.fail_other_error = mo.set.NewCounter(`atlas_api0_player_pdata_requests_total{result="fail_other_error"}`) mo.player_pdata_requests_total.http_method_not_allowed = mo.set.NewCounter(`atlas_api0_player_pdata_requests_total{result="http_method_not_allowed"}`) + mo.rule_evaluation_time_seconds = mo.set.NewHistogram(`atlas_api0_rule_evaluation_time_seconds`) }) // ensure we initialized everything