plugins/logs: Include http request context in decision logs

It would be useful if users had the ability to enhance the
decision log with info from the incoming HTTP request such as
headers. This change allows users to configure headers whose
values if present in the incoming HTTP request would be
surfaced via the decision log. This can be extended in the
future to include more context from the request.

Fixes: #6693

Signed-off-by: Ashutosh Narkar <anarkar4387@gmail.com>
This commit is contained in:
Ashutosh Narkar
2024-05-15 17:38:26 -07:00
parent a751084bfe
commit a8ac7b38bb
9 changed files with 282 additions and 79 deletions
+21 -19
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/logging"
"github.com/open-policy-agent/opa/metrics"
"github.com/open-policy-agent/opa/storage"
"github.com/open-policy-agent/opa/topdown"
@@ -15,25 +16,26 @@ import (
// Info contains information describing a policy decision.
type Info struct {
Txn storage.Transaction
Revision string // Deprecated: Use `Bundles` instead
Bundles map[string]BundleInfo
DecisionID string
TraceID string
SpanID string
RemoteAddr string
Query string
Path string
Timestamp time.Time
Input *interface{}
InputAST ast.Value
Results *interface{}
MappedResults *interface{}
NDBuiltinCache *interface{}
Error error
Metrics metrics.Metrics
Trace []*topdown.Event
RequestID uint64
Txn storage.Transaction
Revision string // Deprecated: Use `Bundles` instead
Bundles map[string]BundleInfo
DecisionID string
TraceID string
SpanID string
RemoteAddr string
HTTPRequestContext logging.HTTPRequestContext
Query string
Path string
Timestamp time.Time
Input *interface{}
InputAST ast.Value
Results *interface{}
MappedResults *interface{}
NDBuiltinCache *interface{}
Error error
Metrics metrics.Metrics
Trace []*topdown.Event
RequestID uint64
}
// BundleInfo contains information describing a bundle.
+15 -14
View File
@@ -2958,20 +2958,21 @@ func (l decisionLogger) Log(ctx context.Context, txn storage.Transaction, path s
decisionID, _ := logging.DecisionIDFromContext(ctx)
info := &Info{
Txn: txn,
Revision: l.revision,
Bundles: bundles,
Timestamp: time.Now().UTC(),
DecisionID: decisionID,
RemoteAddr: rctx.ClientAddr,
Path: path,
Query: query,
Input: goInput,
InputAST: astInput,
Results: goResults,
Error: err,
Metrics: m,
RequestID: rctx.ReqID,
Txn: txn,
Revision: l.revision,
Bundles: bundles,
Timestamp: time.Now().UTC(),
DecisionID: decisionID,
RemoteAddr: rctx.ClientAddr,
HTTPRequestContext: rctx.HTTPRequestContext,
Path: path,
Query: query,
Input: goInput,
InputAST: astInput,
Results: goResults,
Error: err,
Metrics: m,
RequestID: rctx.ReqID,
}
if ndbCache != nil {
+45
View File
@@ -3491,6 +3491,51 @@ func TestDecisionIDs(t *testing.T) {
}
}
func TestDecisionLoggingWithHTTPRequestContext(t *testing.T) {
f := newFixture(t)
decisions := []*Info{}
var nextID int
f.server = f.server.WithDecisionIDFactory(func() string {
nextID++
return fmt.Sprint(nextID)
}).WithDecisionLoggerWithErr(func(_ context.Context, info *Info) error {
decisions = append(decisions, info)
return nil
})
req := newReqV1("POST", "/data/nonexistent", `{"input": {"foo": 1}}`)
req.Header.Set("foo", "bar")
req.Header.Set("foo2", "bar2")
req.Header.Add("foo2", "bar3")
var rctx logging.RequestContext
rctx.HTTPRequestContext = logging.HTTPRequestContext{Header: req.Header.Clone()}
req = req.WithContext(logging.NewContext(req.Context(), &rctx))
if err := f.executeRequest(req, http.StatusOK, `{"decision_id": "1"}`); err != nil {
t.Fatal(err)
}
if len(decisions) != 1 {
t.Fatalf("Expected exactly 1 decision but got: %d", len(decisions))
}
expHeaders := http.Header{}
expHeaders.Set("foo", "bar")
expHeaders.Add("foo2", "bar2")
expHeaders.Add("foo2", "bar3")
exp := logging.HTTPRequestContext{Header: expHeaders}
if !reflect.DeepEqual(decisions[0].HTTPRequestContext, exp) {
t.Fatalf("Expected HTTP request context %v but got: %v", exp, decisions[0].HTTPRequestContext)
}
}
func TestDecisionLogging(t *testing.T) {
f := newFixture(t)