Preparing for v1 API

Moving (most) source to v1 root package to prepare for v0/v1 API separation.

Signed-off-by: Johan Fylling <johan.dev@fylling.se>
This commit is contained in:
Johan Fylling
2024-11-21 16:35:26 +01:00
parent b4e2909bda
commit 7bb6dbe36b
5064 changed files with 1332 additions and 1428 deletions
-64
View File
@@ -1,64 +0,0 @@
// Copyright 2018 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package logs
import (
"container/list"
)
// logBuffer implements a circular FIFO buffer for the plugin that caps memory
// usage at the configured limit. If the buffer size is exceeded, events from
// the front of the buffer are dropped.
type logBuffer struct {
usage int64
limit int64
l *list.List
}
type logBufferElem struct {
bs []byte
}
func newLogBuffer(limit int64) *logBuffer {
return &logBuffer{
limit: limit,
usage: 0,
l: list.New(),
}
}
func (lb *logBuffer) Push(bs []byte) (dropped int) {
size := int64(len(bs))
if lb.limit > 0 {
for elem := lb.l.Front(); elem != nil && (lb.usage+size > lb.limit); elem = elem.Next() {
drop := elem.Value.(logBufferElem).bs
lb.l.Remove(elem)
lb.usage -= int64(len(drop))
dropped++
}
}
elem := logBufferElem{bs}
lb.l.PushBack(elem)
lb.usage += size
return dropped
}
func (lb *logBuffer) Pop() []byte {
elem := lb.l.Front()
if elem != nil {
e := elem.Value.(logBufferElem)
lb.usage -= int64(len(e.bs))
lb.l.Remove(elem)
return e.bs
}
return nil
}
func (lb *logBuffer) Len() int {
return lb.l.Len()
}
-56
View File
@@ -1,56 +0,0 @@
// Copyright 2018 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package logs
import (
"bytes"
"testing"
)
func TestLogBuffer(t *testing.T) {
buffer := newLogBuffer(int64(20)) // 20 byte limit for test purposes
dropped := buffer.Push(make([]byte, 20))
if dropped != 0 {
t.Fatal("Expected dropped to be zero")
}
bs := buffer.Pop()
if len(bs) != 20 {
t.Fatal("Expected buffer size to be 20")
}
bs = buffer.Pop()
if bs != nil {
t.Fatal("Expected buffer to be nil")
}
dropped = buffer.Push(bytes.Repeat([]byte(`1`), 10))
if dropped != 0 {
t.Fatal("Expected dropped to be zero")
}
dropped = buffer.Push(bytes.Repeat([]byte(`2`), 10))
if dropped != 0 {
t.Fatal("Expected dropped to be zero")
}
dropped = buffer.Push(bytes.Repeat([]byte(`3`), 10))
if dropped != 1 {
t.Fatal("Expected dropped to be 1")
}
bs = buffer.Pop()
exp := bytes.Repeat([]byte(`2`), 10)
if !bytes.Equal(bs, exp) {
t.Fatalf("Expected %v but got %v", exp, bs)
}
if buffer.usage != 10 {
t.Fatalf("Expected buffer usage to be 10 but got %v", buffer.usage)
}
}
-238
View File
@@ -1,238 +0,0 @@
// Copyright 2018 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package logs
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"math"
"github.com/open-policy-agent/opa/metrics"
)
const (
encHardLimitThreshold = 0.9
softLimitBaseFactor = 2
softLimitExponentScaleFactor = 0.2
encLogExUploadSizeLimitCounterName = "enc_log_exceeded_upload_size_limit_bytes"
encSoftLimitScaleUpCounterName = "enc_soft_limit_scale_up"
encSoftLimitScaleDownCounterName = "enc_soft_limit_scale_down"
encSoftLimitStableCounterName = "enc_soft_limit_stable"
)
// chunkEncoder implements log buffer chunking and compression. Log events are
// written to the encoder and the encoder outputs chunks that are fit to the
// configured limit.
type chunkEncoder struct {
limit int64
softLimit int64
softLimitScaleUpExponent float64
softLimitScaleDownExponent float64
bytesWritten int
buf *bytes.Buffer
w *gzip.Writer
metrics metrics.Metrics
}
func newChunkEncoder(limit int64) *chunkEncoder {
enc := &chunkEncoder{
limit: limit,
softLimit: limit,
softLimitScaleUpExponent: 0,
softLimitScaleDownExponent: 0,
}
enc.update()
return enc
}
func (enc *chunkEncoder) WithMetrics(m metrics.Metrics) *chunkEncoder {
enc.metrics = m
return enc
}
func (enc *chunkEncoder) Write(event EventV1) (result [][]byte, err error) {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(event); err != nil {
return nil, err
}
return enc.WriteBytes(buf.Bytes())
}
func (enc *chunkEncoder) WriteBytes(bs []byte) (result [][]byte, err error) {
if len(bs) == 0 {
return nil, nil
} else if int64(len(bs)+2) > enc.limit {
if enc.metrics != nil {
enc.metrics.Counter(encLogExUploadSizeLimitCounterName).Incr()
}
return nil, fmt.Errorf("upload chunk size (%d) exceeds upload_size_limit_bytes (%d)",
int64(len(bs)+2), enc.limit)
}
if int64(len(bs)+enc.bytesWritten+1) > enc.softLimit {
if err := enc.writeClose(); err != nil {
return nil, err
}
result, err = enc.reset()
if err != nil {
return nil, err
}
}
if enc.bytesWritten == 0 {
n, err := enc.w.Write([]byte(`[`))
if err != nil {
return nil, err
}
enc.bytesWritten += n
} else {
n, err := enc.w.Write([]byte(`,`))
if err != nil {
return nil, err
}
enc.bytesWritten += n
}
n, err := enc.w.Write(bs)
if err != nil {
return nil, err
}
enc.bytesWritten += n
return
}
func (enc *chunkEncoder) writeClose() error {
if _, err := enc.w.Write([]byte(`]`)); err != nil {
return err
}
return enc.w.Close()
}
func (enc *chunkEncoder) Flush() ([][]byte, error) {
if enc.bytesWritten == 0 {
return nil, nil
}
if err := enc.writeClose(); err != nil {
return nil, err
}
return enc.reset()
}
//nolint:unconvert
func (enc *chunkEncoder) reset() ([][]byte, error) {
// Adjust the encoder's soft limit based on the current amount of
// data written to the underlying buffer. The soft limit decides when to flush a chunk.
// The soft limit is modified based on the below algorithm:
// 1) Scale Up: If the current chunk size is within 90% of the user-configured limit, exponentially increase
// the soft limit. The exponential function is 2^x where x has a minimum value of 1
// 2) Scale Down: If the current chunk size exceeds the hard limit, decrease the soft limit and re-encode the
// decisions in the last chunk.
// 3) Equilibrium: If the chunk size is between 90% and 100% of the user-configured limit, maintain soft limit value.
if enc.buf.Len() < int(float64(enc.limit)*encHardLimitThreshold) {
if enc.metrics != nil {
enc.metrics.Counter(encSoftLimitScaleUpCounterName).Incr()
}
mul := int64(math.Pow(float64(softLimitBaseFactor), float64(enc.softLimitScaleUpExponent+1)))
enc.softLimit *= mul
enc.softLimitScaleUpExponent += softLimitExponentScaleFactor
return enc.update(), nil
}
if int(enc.limit) > enc.buf.Len() && enc.buf.Len() >= int(float64(enc.limit)*encHardLimitThreshold) {
if enc.metrics != nil {
enc.metrics.Counter(encSoftLimitStableCounterName).Incr()
}
enc.softLimitScaleDownExponent = enc.softLimitScaleUpExponent
return enc.update(), nil
}
if enc.softLimit > enc.limit {
if enc.metrics != nil {
enc.metrics.Counter(encSoftLimitScaleDownCounterName).Incr()
}
if enc.softLimitScaleDownExponent < enc.softLimitScaleUpExponent {
enc.softLimitScaleDownExponent = enc.softLimitScaleUpExponent
}
den := int64(math.Pow(float64(softLimitBaseFactor), float64(enc.softLimitScaleDownExponent-enc.softLimitScaleUpExponent+1)))
enc.softLimit /= den
if enc.softLimitScaleUpExponent > 0 {
enc.softLimitScaleUpExponent -= softLimitExponentScaleFactor
}
}
events, decErr := newChunkDecoder(enc.buf.Bytes()).decode()
if decErr != nil {
return nil, decErr
}
enc.initialize()
var result [][]byte
for _, event := range events {
chunk, err := enc.Write(event)
if err != nil {
return nil, err
}
if chunk != nil {
result = append(result, chunk...)
}
}
return result, nil
}
func (enc *chunkEncoder) update() [][]byte {
buf := enc.buf
enc.initialize()
if buf != nil {
return [][]byte{buf.Bytes()}
}
return nil
}
func (enc *chunkEncoder) initialize() {
enc.buf = new(bytes.Buffer)
enc.bytesWritten = 0
enc.w = gzip.NewWriter(enc.buf)
}
// chunkDecoder decodes the encoded chunks and outputs the log events
type chunkDecoder struct {
raw []byte
}
func newChunkDecoder(raw []byte) *chunkDecoder {
return &chunkDecoder{
raw: raw,
}
}
func (dec *chunkDecoder) decode() ([]EventV1, error) {
gr, err := gzip.NewReader(bytes.NewReader(dec.raw))
if err != nil {
return nil, err
}
var events []EventV1
if err := json.NewDecoder(gr).Decode(&events); err != nil {
return nil, err
}
gr.Close()
return events, nil
}
-181
View File
@@ -1,181 +0,0 @@
// Copyright 2018 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package logs
import (
"fmt"
"testing"
"time"
"github.com/open-policy-agent/opa/metrics"
)
func TestChunkEncoder(t *testing.T) {
enc := newChunkEncoder(1000)
var result interface{} = false
var expInput interface{} = map[string]interface{}{"method": "GET"}
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
if err != nil {
panic(err)
}
event := EventV1{
Labels: map[string]string{
"id": "test-instance-id",
"app": "example-app",
},
Revision: "a",
DecisionID: "a",
Path: "foo/bar",
Input: &expInput,
Result: &result,
RequestedBy: "test",
Timestamp: ts,
}
bs, err := enc.Write(event)
if bs != nil || err != nil {
t.Fatalf("Unexpected error or chunk produced: err: %v", err)
}
bs, err = enc.Flush()
if bs == nil || err != nil {
t.Fatalf("Unexpected error or NO chunk produced: err: %v", err)
}
bs, err = enc.Flush()
if bs != nil || err != nil {
t.Fatalf("Unexpected error chunk produced: err: %v", err)
}
}
func TestChunkEncoderSizeLimit(t *testing.T) {
enc := newChunkEncoder(1).WithMetrics(metrics.New())
var result interface{} = false
var expInput interface{} = map[string]interface{}{"method": "GET"}
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
if err != nil {
t.Fatal(err)
}
event := EventV1{
Labels: map[string]string{
"id": "test-instance-id",
"app": "example-app",
},
DecisionID: "123",
Path: "foo/bar",
Input: &expInput,
Result: &result,
RequestedBy: "test",
Timestamp: ts,
}
_, err = enc.Write(event)
if err == nil {
t.Error("Expected error as upload chunk size exceeds configured limit")
}
expected := "upload chunk size (200) exceeds upload_size_limit_bytes (1)"
if err.Error() != expected {
t.Errorf("expected: '%s', got: '%s'", expected, err.Error())
}
}
func TestChunkEncoderAdaptive(t *testing.T) {
enc := newChunkEncoder(1000).WithMetrics(metrics.New())
var result interface{} = false
var expInput interface{} = map[string]interface{}{"method": "GET"}
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
if err != nil {
panic(err)
}
var chunks [][]byte
numEvents := 400
for i := 0; i < numEvents; i++ {
bundles := map[string]BundleInfoV1{}
bundles["authz"] = BundleInfoV1{Revision: fmt.Sprint(i)}
event := EventV1{
Labels: map[string]string{
"id": "test-instance-id",
"app": "example-app",
},
Bundles: bundles,
DecisionID: fmt.Sprint(i),
Path: "foo/bar",
Input: &expInput,
Result: &result,
RequestedBy: "test",
Timestamp: ts,
}
chunk, err := enc.Write(event)
if err != nil {
t.Fatal(err)
}
if chunk != nil {
chunks = append(chunks, chunk...)
}
}
// decode the chunks and check the number of events is equal to the encoded events
numEventsActual := decodeChunks(t, chunks)
// flush the encoder
for {
bs, err := enc.Flush()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if len(bs) == 0 {
break
}
numEventsActual += decodeChunks(t, bs)
}
if numEvents != numEventsActual {
t.Fatalf("Expected %v events but got %v", numEvents, numEventsActual)
}
actualScaleUpEvents := enc.metrics.Counter(encSoftLimitScaleUpCounterName).Value().(uint64)
actualScaleDownEvents := enc.metrics.Counter(encSoftLimitScaleDownCounterName).Value().(uint64)
actualEquiEvents := enc.metrics.Counter(encSoftLimitStableCounterName).Value().(uint64)
expectedScaleUpEvents := uint64(8)
expectedScaleDownEvents := uint64(3)
expectedEquiEvents := uint64(0)
if actualScaleUpEvents != expectedScaleUpEvents {
t.Fatalf("Expected scale up events %v but got %v", expectedScaleUpEvents, actualScaleUpEvents)
}
if actualScaleDownEvents != expectedScaleDownEvents {
t.Fatalf("Expected scale down events %v but got %v", expectedScaleDownEvents, actualScaleDownEvents)
}
if actualEquiEvents != expectedEquiEvents {
t.Fatalf("Expected equilibrium events %v but got %v", expectedEquiEvents, actualEquiEvents)
}
}
func decodeChunks(t *testing.T, bs [][]byte) int {
t.Helper()
numEvents := 0
for _, chunk := range bs {
events, err := newChunkDecoder(chunk).decode()
if err != nil {
t.Fatal(err)
}
numEvents += len(events)
}
return numEvents
}
-362
View File
@@ -1,362 +0,0 @@
// Copyright 2020 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package logs
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/open-policy-agent/opa/internal/deepcopy"
)
type maskOP string
const (
maskOPRemove maskOP = "remove"
maskOPUpsert maskOP = "upsert"
partInput = "input"
partResult = "result"
partNDBCache = "nd_builtin_cache"
)
var (
errMaskInvalidObject = fmt.Errorf("mask upsert invalid object")
)
type maskRule struct {
OP maskOP `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
escapedParts []string
modifyFullObj bool
failUndefinedPath bool
}
type maskRuleSet struct {
OnRuleError func(*maskRule, error)
Rules []*maskRule
resultCopied bool
}
func (r maskRule) String() string {
return "/" + strings.Join(r.escapedParts, "/")
}
type maskRuleOption func(*maskRule) error
func newMaskRule(path string, opts ...maskRuleOption) (*maskRule, error) {
const (
defaultOP = maskOPRemove
defaultFailUndefinedPath = false
)
if len(path) == 0 {
return nil, fmt.Errorf("mask must be non-empty")
} else if !strings.HasPrefix(path, "/") {
return nil, fmt.Errorf("mask must be slash-prefixed")
}
parts := strings.Split(path[1:], "/")
switch parts[0] {
case partInput, partResult, partNDBCache: // OK
default:
return nil, fmt.Errorf("mask prefix not allowed: %v", parts[0])
}
escapedParts := make([]string, len(parts))
for i := range parts {
_, err := url.PathUnescape(parts[i])
if err != nil {
return nil, err
}
escapedParts[i] = url.PathEscape(parts[i])
}
modifyFullObj := false
if len(escapedParts) == 1 {
modifyFullObj = true
}
r := &maskRule{
OP: defaultOP,
Path: path,
escapedParts: escapedParts,
failUndefinedPath: defaultFailUndefinedPath,
modifyFullObj: modifyFullObj,
}
for _, opt := range opts {
if err := opt(r); err != nil {
return nil, err
}
}
return r, nil
}
func withOP(op maskOP) maskRuleOption {
return func(r *maskRule) error {
switch op {
case maskOPRemove, maskOPUpsert:
r.OP = op
return nil
}
return fmt.Errorf("mask op is not supported: %s", op)
}
}
func withValue(val interface{}) maskRuleOption {
return func(r *maskRule) error {
r.Value = val
return nil
}
}
func withFailUndefinedPath() maskRuleOption {
return func(r *maskRule) error {
r.failUndefinedPath = true
return nil
}
}
func (r maskRule) Mask(event *EventV1) error {
var maskObj *interface{} // pointer to event Input|Result|NDBCache object
var maskObjPtr **interface{} // pointer to the event Input|Result|NDBCache pointer itself
switch p := r.escapedParts[0]; p {
case partInput:
if event.Input == nil {
if r.failUndefinedPath {
return errMaskInvalidObject
}
return nil
}
maskObj = event.Input
maskObjPtr = &event.Input
case partResult:
if event.Result == nil {
if r.failUndefinedPath {
return errMaskInvalidObject
}
return nil
}
maskObj = event.Result
maskObjPtr = &event.Result
case partNDBCache:
if event.NDBuiltinCache == nil {
if r.failUndefinedPath {
return errMaskInvalidObject
}
return nil
}
maskObj = event.NDBuiltinCache
maskObjPtr = &event.NDBuiltinCache
default:
return fmt.Errorf("illegal path value: %s", p)
}
switch r.OP {
case maskOPRemove:
if r.modifyFullObj {
*maskObjPtr = nil
} else {
parent, err := r.lookup(r.escapedParts[1:len(r.escapedParts)-1], *maskObj)
if err != nil {
if err == errMaskInvalidObject && r.failUndefinedPath {
return err
}
}
parentObj, ok := parent.(map[string]interface{})
if !ok {
return nil
}
fld := r.escapedParts[len(r.escapedParts)-1]
if _, ok := parentObj[fld]; !ok {
return nil
}
delete(parentObj, fld)
}
event.Erased = append(event.Erased, r.String())
case maskOPUpsert:
if r.modifyFullObj {
*maskObjPtr = &r.Value
} else {
inputObj, ok := (*maskObj).(map[string]interface{})
if !ok {
return nil
}
if err := r.mkdirp(inputObj, r.escapedParts[1:len(r.escapedParts)], r.Value); err != nil {
if r.failUndefinedPath {
return err
}
return nil
}
}
event.Masked = append(event.Masked, r.String())
default:
return fmt.Errorf("illegal mask op value: %s", r.OP)
}
return nil
}
func (r maskRule) lookup(p []string, node interface{}) (interface{}, error) {
for i := 0; i < len(p); i++ {
switch v := node.(type) {
case map[string]interface{}:
var ok bool
if node, ok = v[p[i]]; !ok {
return nil, errMaskInvalidObject
}
case []interface{}:
idx, err := strconv.Atoi(p[i])
if err != nil {
return nil, errMaskInvalidObject
} else if idx < 0 || idx >= len(v) {
return nil, errMaskInvalidObject
}
node = v[idx]
default:
return nil, errMaskInvalidObject
}
}
return node, nil
}
func (r maskRule) mkdirp(node map[string]interface{}, path []string, value interface{}) error {
if len(path) == 0 {
return nil
}
// create intermediate nodes
for i := 0; i < len(path)-1; i++ {
child, ok := node[path[i]]
if !ok {
child := map[string]interface{}{}
node[path[i]] = child
node = child
continue
}
switch obj := child.(type) {
case map[string]interface{}:
node = obj
default:
return errMaskInvalidObject
}
}
node[path[len(path)-1]] = value
return nil
}
func newMaskRuleSet(rv interface{}, onRuleError func(*maskRule, error)) (*maskRuleSet, error) {
var mRuleSet = &maskRuleSet{
OnRuleError: onRuleError,
}
rawRules, ok := rv.([]interface{})
if !ok {
return nil, fmt.Errorf("unexpected rule format %v (%[1]T)", rv)
}
for _, iface := range rawRules {
switch v := iface.(type) {
case string:
// preserve default behavior of remove when
// structured mask format is not provided
rule, err := newMaskRule(v)
if err != nil {
return nil, err
}
mRuleSet.Rules = append(mRuleSet.Rules, rule)
case map[string]interface{}:
rule := &maskRule{}
op, set := getString(v, "op")
if set && op == "" {
return nil, fmt.Errorf("invalid \"op\" value: %v %[1]T", v["op"])
}
rule.OP = maskOP(op)
path, set := getString(v, "path")
if set && path == "" {
return nil, fmt.Errorf("invalid \"path\" value: %v %[1]T", v["path"])
}
rule.Path = path
rule.Value = v["value"]
// use unmarshalled values to create new Mask Rule
rule, err := newMaskRule(rule.Path, withOP(rule.OP), withValue(rule.Value))
// TODO add withFailUndefinedPath() option based on
// A) new syntax in user defined mask rule
// B) passed in/global configuration option
// rule precedence A>B
if err != nil {
return nil, err
}
mRuleSet.Rules = append(mRuleSet.Rules, rule)
default:
return nil, fmt.Errorf("invalid mask rule format encountered: %T", v)
}
}
return mRuleSet, nil
}
func (rs maskRuleSet) Mask(event *EventV1) {
for _, mRule := range rs.Rules {
// result must be deep copied if there are any mask rules
// targeting it, to avoid modifying the result sent
// to the consumer
if mRule.escapedParts[0] == partResult && event.Result != nil && !rs.resultCopied {
resultCopy := deepcopy.DeepCopy(*event.Result)
event.Result = &resultCopy
rs.resultCopied = true
}
err := mRule.Mask(event)
if err != nil {
rs.OnRuleError(mRule, err)
}
}
}
// bool return means the field was set, if the string is still "", the
// value was invalid
func getString(x map[string]any, key string) (string, bool) {
y, ok := x[key]
if !ok {
return "", false
}
s, ok := y.(string)
if !ok {
return "", true
}
return s, true
}
-766
View File
@@ -1,766 +0,0 @@
// Copyright 2020 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package logs
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
"github.com/open-policy-agent/opa/util"
)
func TestNewMaskRule(t *testing.T) {
tests := []struct {
note string
input *maskRule
expErr error
expPtr *maskRule
}{
{
note: "empty",
input: &maskRule{
OP: maskOPRemove,
Path: "",
},
expErr: fmt.Errorf("mask must be non-empty"),
},
{
note: "missing slash",
input: &maskRule{
OP: maskOPRemove,
Path: "foo",
},
expErr: fmt.Errorf("mask must be slash-prefixed"),
},
{
note: "no prefix",
input: &maskRule{
OP: maskOPRemove,
Path: "/",
},
expErr: fmt.Errorf("mask prefix not allowed"),
},
{
note: "bad prefix key",
input: &maskRule{
OP: maskOPRemove,
Path: "/labels/foo",
},
expErr: fmt.Errorf("mask prefix not allowed"),
},
{
note: "standard",
input: &maskRule{
OP: maskOPRemove,
Path: "/input/a/b/c",
},
expPtr: &maskRule{
OP: maskOPRemove,
Path: "/input/a/b/c",
escapedParts: []string{"input", "a", "b", "c"},
},
},
{
note: "fail with object path undefined",
input: &maskRule{
OP: maskOPRemove,
Path: "/input/a/b/c",
failUndefinedPath: true,
},
expPtr: &maskRule{
OP: maskOPRemove,
Path: "/input/a/b/c",
failUndefinedPath: true,
escapedParts: []string{"input", "a", "b", "c"},
},
},
{
note: "fail with invalid OP",
input: &maskRule{
OP: maskOP("undefinedOP"),
Path: "/input/a/b/c",
},
expErr: fmt.Errorf("mask op is not supported: undefinedOP"),
},
{
note: "escaping",
input: &maskRule{
OP: maskOPRemove,
Path: "/input/a/%2F%2F/b",
},
expPtr: &maskRule{
OP: maskOPRemove,
Path: "/input/a/%2F%2F/b",
escapedParts: []string{"input", "a", "%252F%252F", "b"},
},
},
{
note: "bad escape",
input: &maskRule{
OP: maskOPRemove,
Path: "/input/a/%F/b",
},
expErr: fmt.Errorf("invalid URL escape"),
},
{
note: "empty component",
input: &maskRule{
OP: maskOPRemove,
Path: "/input//foo",
},
expPtr: &maskRule{
OP: maskOPRemove,
Path: "/input//foo",
escapedParts: []string{"input", "", "foo"},
},
},
{
note: "result",
input: &maskRule{
OP: maskOPRemove,
Path: "/result/a",
},
expPtr: &maskRule{
OP: maskOPRemove,
Path: "/result/a",
escapedParts: []string{"result", "a"},
},
},
{
note: "root",
input: &maskRule{
OP: maskOPRemove,
Path: "/input",
},
expPtr: &maskRule{
OP: maskOPRemove,
Path: "/input",
escapedParts: []string{"input"},
modifyFullObj: true,
},
},
{
note: "unsupported mask op",
input: &maskRule{
OP: maskOP("unsupported"),
Path: "/input",
},
expErr: fmt.Errorf("mask op is not supported: unsupported"),
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
result, err := newMaskRule(tc.input.Path, withOP(tc.input.OP), withValue(tc.input.Value))
if tc.input.failUndefinedPath {
_ = withFailUndefinedPath()(result)
}
if tc.expErr != nil {
if err == nil {
t.Fatalf("Expected error but got: %v", result)
} else if !strings.Contains(err.Error(), tc.expErr.Error()) {
t.Fatalf("Expected error: %v, but got error: %v", tc.expErr, err)
}
} else {
if err != nil {
t.Fatal("Unexpected error:", err)
}
if !reflect.DeepEqual(result, tc.expPtr) {
t.Fatalf("Expected %#+v but got %#+v", tc.expPtr, result)
}
}
})
}
}
func TestMaskRuleMask(t *testing.T) {
tests := []struct {
note string
ptr *maskRule
event string
exp string
expErr error
}{
{
note: "erase input",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input",
},
event: `{"input": {"a": 1}}`,
exp: `{"erased": ["/input"]}`,
},
{
note: "upsert input",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input",
Value: struct {
RandoString string
}{RandoString: "foo"},
},
event: `{"input": {"a": 1}}`,
exp: `{"masked": ["/input"], "input": {"RandoString": "foo"}}`,
},
{
note: "erase result",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/result",
},
event: `{"result": "foo"}`,
exp: `{"erased": ["/result"]}`,
},
{
note: "upsert result",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/result",
Value: "upserted",
},
event: `{"result": "foo"}`,
exp: `{"masked": ["/result"], "result": "upserted"}`,
},
{
note: "erase undefined input",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo",
},
event: `{}`,
exp: `{}`,
},
{
note: "erase undefined input: fail unknown object path on",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo",
failUndefinedPath: true,
},
event: `{}`,
exp: `{}`,
expErr: errMaskInvalidObject,
},
{
note: "upsert undefined input",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo",
},
event: `{}`,
exp: `{}`,
},
{
note: "upsert undefined input: fail unknown object path on",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo",
failUndefinedPath: true,
},
event: `{}`,
exp: `{}`,
expErr: errMaskInvalidObject,
},
{
note: "erase undefined result",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/result/foo",
},
event: `{}`,
exp: `{}`,
},
{
note: "erase undefined result: fail unknown object path on",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/result/foo",
failUndefinedPath: true,
},
event: `{}`,
exp: `{}`,
expErr: errMaskInvalidObject,
},
{
note: "upsert undefined result",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/result/foo",
},
event: `{}`,
exp: `{}`,
},
{
note: "upsert undefined result: fail unknown object path on",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/result/foo",
failUndefinedPath: true,
},
event: `{}`,
exp: `{}`,
expErr: errMaskInvalidObject,
},
{
note: "erase undefined node",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo",
},
event: `{"input": {"bar": 1}}`,
exp: `{"input": {"bar": 1}}`,
},
{
note: "erase undefined node: fail unknown object path on",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo",
failUndefinedPath: true,
},
event: `{"input": {"bar": 1}}`,
exp: `{"input": {"bar": 1}}`,
expErr: errMaskInvalidObject,
},
{
note: "upsert undefined node with nil value",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo",
},
event: `{"input": {"bar": 1}}`,
exp: `{"input": {"bar": 1, "foo": null}, "masked": ["/input/foo"]}`,
},
{
note: "upsert undefined node with nil value: fail unknown object path on",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo",
failUndefinedPath: true,
},
event: `{"input": {"bar": 1}}`,
exp: `{"input": {"bar": 1, "foo": null}, "masked": ["/input/foo"]}`,
expErr: errMaskInvalidObject,
},
{
note: "upsert undefined node with a value",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo",
Value: "upserted",
},
event: `{"input": {"bar": 1}}`,
exp: `{"input": {"bar": 1, "foo": "upserted"}, "masked": ["/input/foo"]}`,
},
{
note: "erase undefined node-2",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo/bar",
},
event: `{"input": {"foo": 1}}`,
exp: `{"input": {"foo": 1}}`,
},
{
note: "upsert unsupported nested object type (json.Number) #1",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/bar",
},
event: `{"input": {"foo": 1}}`,
exp: `{"input": {"foo": 1}}`,
},
{
note: "upsert unsupported nested object type (string) #1",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/bar",
},
event: `{"input": {"foo": "bar"}}`,
exp: `{"input": {"foo": "bar"}}`,
},
{
note: "erase: undefined object: missing key",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo/bar/baz",
},
event: `{"input": {"foo": {}}}`,
exp: `{"input": {"foo": {}}}`,
},
{
note: "upsert: undefined object: missing key, no value",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/bar/baz",
},
event: `{"input": {"foo": {}}}`,
exp: `{"input": {"foo": {"bar": {"baz": null}}}, "masked": ["/input/foo/bar/baz"]}`,
},
{
note: "upsert: undefined object: missing key, provided value",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/bar/baz",
Value: 100,
},
event: `{"input": {"foo": {}}}`,
exp: `{"input": {"foo": {"bar": {"baz": 100}}}, "masked": ["/input/foo/bar/baz"]}`,
},
{
note: "erase: undefined scalar",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo/bar/baz",
},
event: `{"input": {"foo": 1}}`,
exp: `{"input": {"foo": 1}}`,
},
{
note: "upsert: unsupported nested object type (json.Number) #2",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/bar/baz",
},
event: `{"input": {"foo": 1}}`,
exp: `{"input": {"foo": 1}}`,
},
{
note: "erase: undefined array: non-int index",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo/bar/baz", // bar is invalid
},
event: `{"input": {"foo": [{"baz": 1}]}}`,
exp: `{"input": {"foo": [{"baz": 1}]}}`,
},
{
note: "upsert: unsupported type: []interface {}",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/bar/baz", // foo is []interface
},
event: `{"input": {"foo": [{"baz": 1}]}}`,
exp: `{"input": {"foo": [{"baz": 1}]}}`,
},
{
note: "erase: undefined array: negative index",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo/-1/baz",
},
event: `{"input": {"foo": [{"baz": 1}]}}`,
exp: `{"input": {"foo": [{"baz": 1}]}}`,
},
{
note: "upsert: undefined array: negative index",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/-1/baz", // foo is an []interface {}
},
event: `{"input": {"foo": [{"baz": 1}]}}`,
exp: `{"input": {"foo": [{"baz": 1}]}}`,
},
{
note: "erase: undefined array: index out of range",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo/1/baz",
},
event: `{"input": {"foo": [{"baz": 1}]}}`,
exp: `{"input": {"foo": [{"baz": 1}]}}`,
},
{
note: "upsert: unsupported nested object type (array) #1",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/1/baz", // foo is an []interface {}
},
event: `{"input": {"foo": [{"baz": 1}]}}`,
exp: `{"input": {"foo": [{"baz": 1}]}}`,
},
{
note: "erase: undefined array: remove element",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo/0",
},
event: `{"input": {"foo": [1]}}`,
exp: `{"input": {"foo": [1]}}`,
},
{
note: "upsert: unsupported nested object type (array) #2",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo/0",
},
event: `{"input": {"foo": [1]}}`,
exp: `{"input": {"foo": [1]}}`,
},
{
note: "erase: object key",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo",
},
event: `{"input": {"bar": 1, "foo": [{"baz": 1}]}}`,
exp: `{"input": {"bar": 1}, "erased": ["/input/foo"]}`,
},
{
note: "upsert: object key",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/foo",
Value: []map[string]int{{"nabs": 1}},
},
event: `{"input": {"bar": 1, "foo": [{"baz": 1}]}}`,
exp: `{"input": {"bar": 1, "foo": [{"nabs": 1}]}, "masked": ["/input/foo"]}`,
},
{
note: "erase: object key (multiple)",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/bar",
},
event: `{"input": {"bar": 1}, "erased": ["/input/foo"]}`,
exp: `{"input": {}, "erased": ["/input/foo", "/input/bar"]}`,
},
{
note: "erase: object key (nested array)",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/foo/0/bar",
},
event: `{"input": {"foo": [{"bar": 1, "baz": 2}]}}`,
exp: `{"input": {"foo": [{"baz": 2}]}, "erased": ["/input/foo/0/bar"]}`,
},
{
note: "erase input: special character in path",
ptr: &maskRule{
OP: maskOPRemove,
Path: "/input/:path",
},
event: `{"input": {"bar": 1, ":path": "token"}}`,
exp: `{"input": {"bar": 1}, "erased": ["/input/:path"]}`,
},
{
note: "upsert input: special character in path",
ptr: &maskRule{
OP: maskOPUpsert,
Path: "/input/:path",
Value: "upserted",
},
event: `{"input": {"bar": 1, ":path": "token"}}`,
exp: `{"input": {"bar": 1, ":path": "upserted"}, "masked": ["/input/:path"]}`,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
ptr, err := newMaskRule(tc.ptr.Path, withOP(tc.ptr.OP), withValue(tc.ptr.Value))
if tc.ptr.failUndefinedPath {
_ = withFailUndefinedPath()(ptr)
}
if err != nil {
panic(err)
}
var exp EventV1
if err := util.UnmarshalJSON([]byte(tc.exp), &exp); err != nil {
panic(err)
}
var event EventV1
if err := util.UnmarshalJSON([]byte(tc.event), &event); err != nil {
panic(err)
}
err = ptr.Mask(&event)
if err != nil {
if tc.expErr == nil {
t.Fatalf("no expected error, but received '%s'", err.Error())
}
if tc.expErr.Error() != err.Error() {
t.Fatalf("expected error '%s', got '%s'", tc.expErr.Error(), err.Error())
}
}
// compare via json marshall to map tc input types
bs1, _ := json.MarshalIndent(exp, "", " ")
bs2, _ := json.MarshalIndent(event, "", " ")
if !bytes.Equal(bs1, bs2) {
t.Fatalf("Expected: %s\nGot: %s", string(bs1), string(bs2))
}
})
}
}
func TestNewMaskRuleSet(t *testing.T) {
tests := []struct {
note string
value interface{}
exp *maskRuleSet
err error
}{
{
note: "invalid format: not []interface{}",
value: map[string]int{"invalid": 1},
err: fmt.Errorf("unexpected rule format map[invalid:1] (map[string]int)"),
},
{
note: "invalid format: nested type not string or map[string]interface{}",
value: []interface{}{
[]int{1, 2},
},
err: fmt.Errorf("invalid mask rule format encountered: []int"),
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
_, err := newMaskRuleSet(tc.value, func(_ *maskRule, _ error) {})
if err != nil {
if exp, act := tc.err.Error(), err.Error(); exp != act {
t.Fatalf("Expected: %s\nGot: %s", exp, act)
}
} else if tc.err != nil {
t.Errorf("expected error %v, got nil", tc.err)
}
})
}
}
func TestMaskRuleSetMask(t *testing.T) {
tests := []struct {
note string
rules []*maskRule
event string
exp string
expErr error
}{
{
note: "erase input",
rules: []*maskRule{
{
OP: maskOPRemove,
Path: "/input",
},
},
event: `{"input": {"a": 1}}`,
exp: `{"erased": ["/input"]}`,
},
{
note: "erase result",
rules: []*maskRule{
{
OP: maskOPRemove,
Path: "/result",
},
},
event: `{"result": {"a": 1}}`,
exp: `{"erased": ["/result"]}`,
},
{
note: "erase input and result nested",
rules: []*maskRule{
{
OP: maskOPRemove,
Path: "/input/a/b",
},
{
OP: maskOPRemove,
Path: "/result/c/d",
},
},
event: `{"input":{"a":{"b":"removeme","y":"stillhere"}},"result":{"c":{"d":"removeme","z":"stillhere"}}}`,
exp: `{"input":{"a":{"y":"stillhere"}},"result":{"c":{"z":"stillhere"}},"erased":["/input/a/b", "/result/c/d"]}`,
},
{
note: "expected rule error",
rules: []*maskRule{
{
OP: maskOPRemove,
Path: "/result",
failUndefinedPath: true,
},
},
event: `{"input":"foo"}`,
exp: `{"input":"foo"}`,
expErr: errMaskInvalidObject,
},
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
ptr := &maskRuleSet{}
var ruleErr error
if tc.expErr != nil {
ptr.OnRuleError = func(_ *maskRule, err error) {
ruleErr = err
}
} else {
ptr.OnRuleError = func(mRule *maskRule, err error) {
t.Fatalf("unexpected rule error, rule: %s, error: %s", mRule.String(), err.Error())
}
}
for _, rule := range tc.rules {
var mRule *maskRule
var err error
if rule.failUndefinedPath {
mRule, err = newMaskRule(rule.Path, withOP(rule.OP), withValue(rule.Value), withFailUndefinedPath())
} else {
mRule, err = newMaskRule(rule.Path, withOP(rule.OP), withValue(rule.Value))
}
if err != nil {
panic(err)
}
ptr.Rules = append(ptr.Rules, mRule)
}
var exp EventV1
if err := util.UnmarshalJSON([]byte(tc.exp), &exp); err != nil {
panic(err)
}
var event EventV1
var origEvent EventV1
if err := util.UnmarshalJSON([]byte(tc.event), &event); err != nil {
panic(err)
}
origEvent = event
ptr.Mask(&event)
// compare via json marshall to map tc input types
bs1, _ := json.MarshalIndent(exp, "", " ")
bs2, _ := json.MarshalIndent(event, "", " ")
if !bytes.Equal(bs1, bs2) {
t.Fatalf("Expected: %s\nGot: %s", string(bs1), string(bs2))
}
if origEvent.Result != nil && reflect.DeepEqual(origEvent.Result, event.Result) {
t.Fatal("Expected event.Result to be deep copied during masking, so that the event's original Result is not modified")
}
if tc.expErr != nil {
if ruleErr == nil {
t.Fatalf("Expected: %s\nGot:%s", tc.expErr.Error(), "nil")
}
if tc.expErr != ruleErr {
t.Fatalf("Expected: %s\nGot:%s", tc.expErr.Error(), ruleErr.Error())
}
}
})
}
}
File diff suppressed because it is too large Load Diff
-267
View File
@@ -1,267 +0,0 @@
package logs
import (
"context"
"fmt"
"testing"
"github.com/open-policy-agent/opa/plugins"
"github.com/open-policy-agent/opa/storage"
inmem "github.com/open-policy-agent/opa/storage/inmem/test"
"github.com/open-policy-agent/opa/util"
)
const largeEvent = `{
"_id": "15596749567705615560",
"decision_id": "0e67fda0-170b-454d-9f5e-29691073f97e",
"input": {
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"request": {
"kind": {
"group": "",
"kind": "Pod",
"version": "v1"
},
"namespace": "demo",
"object": {
"metadata": {
"creationTimestamp": "2019-06-04T19:02:35Z",
"labels": {
"run": "nginx"
},
"name": "nginx",
"namespace": "demo",
"uid": "507e4c3c-86fb-11e9-b289-42010a8000b2"
},
"spec": {
"containers": [
{
"image": "nginx",
"imagePullPolicy": "Always",
"name": "nginx",
"resources": {},
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File",
"volumeMounts": [
{
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
"name": "default-token-5vjbc",
"readOnly": true
}
]
}
],
"dnsPolicy": "ClusterFirst",
"priority": 0,
"restartPolicy": "Never",
"schedulerName": "default-scheduler",
"securityContext": {},
"serviceAccount": "default",
"serviceAccountName": "default",
"terminationGracePeriodSeconds": 30,
"tolerations": [
{
"effect": "NoExecute",
"key": "node.kubernetes.io/not-ready",
"operator": "Exists",
"tolerationSeconds": 300
},
{
"effect": "NoExecute",
"key": "node.kubernetes.io/unreachable",
"operator": "Exists",
"tolerationSeconds": 300
}
],
"volumes": [
{
"name": "default-token-5vjbc",
"secret": {
"secretName": "default-token-5vjbc"
}
}
]
},
"status": {
"phase": "Pending",
"qosClass": "BestEffort"
}
},
"oldObject": null,
"operation": "CREATE",
"resource": {
"group": "",
"resource": "pods",
"version": "v1"
},
"userInfo": {
"groups": [
"system:serviceaccounts",
"system:serviceaccounts:opa-system",
"system:authenticated"
],
"username": "system:serviceaccount:opa-system:default"
}
}
},
"labels": {
"id": "462a43bd-6a5f-4530-9386-30b0f4e0c8af",
"policy-type": "kubernetes/admission_control",
"system-type": "kubernetes",
"version": "0.10.5"
},
"metrics": {
"timer_rego_module_compile_ns": 222,
"timer_rego_module_parse_ns": 313,
"timer_rego_query_compile_ns": 121360,
"timer_rego_query_eval_ns": 923279,
"timer_rego_query_parse_ns": 287152,
"timer_server_handler_ns": 2563846
},
"path": "admission_control/main",
"requested_by": "10.52.0.1:53848",
"result": {
"apiVersion": "admission.k8s.io/v1beta1",
"kind": "AdmissionReview",
"response": {
"allowed": false,
"status": {
"message": "Resource Pod/demo/nginx includes container image 'nginx' from prohibited registry"
}
}
},
"revision": "jafsdkjfhaslkdfjlaksdjflaksjdflkajsdlkfjasldkfjlaksdjflkasdjflkasjdflkajsdflkjasdklfjalsdjf",
"timestamp": "2019-06-04T19:02:35.692Z"
}`
func BenchmarkMaskingNop(b *testing.B) {
ctx := context.Background()
store := inmem.New()
manager, err := plugins.New(nil, "test", store)
if err != nil {
b.Fatal(err)
} else if err := manager.Start(ctx); err != nil {
b.Fatal(err)
}
cfg := &Config{Service: "svc"}
t := plugins.DefaultTriggerMode
if err := cfg.validateAndInjectDefaults([]string{"svc"}, nil, &t); err != nil {
b.Fatal(err)
}
plugin := New(cfg, manager)
var event EventV1
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
b.Fatal(err)
}
input, err := event.AST()
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := plugin.maskEvent(ctx, nil, input, &event); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkMaskingRuleCountsNop(b *testing.B) {
numRules := []int{1, 10, 100, 1000}
ctx := context.Background()
store := inmem.New()
manager, err := plugins.New(nil, "test", store)
if err != nil {
b.Fatal(err)
} else if err := manager.Start(ctx); err != nil {
b.Fatal(err)
}
cfg := &Config{Service: "svc"}
t := plugins.DefaultTriggerMode
if err := cfg.validateAndInjectDefaults([]string{"svc"}, nil, &t); err != nil {
b.Fatal(err)
}
plugin := New(cfg, manager)
var event EventV1
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
b.Fatal(err)
}
input, err := event.AST()
if err != nil {
b.Fatal(err)
}
for _, ruleCount := range numRules {
b.Run(fmt.Sprintf("%dRules", ruleCount), func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := plugin.maskEvent(ctx, nil, input, &event); err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkMaskingErase(b *testing.B) {
ctx := context.Background()
store := inmem.New()
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
return store.UpsertPolicy(ctx, txn, "test.rego", []byte(`
package system.log
mask["/input"] {
input.input.request.kind.kind == "Pod"
}
`))
})
if err != nil {
b.Fatal(err)
}
manager, err := plugins.New(nil, "test", store)
if err != nil {
b.Fatal(err)
} else if err := manager.Start(ctx); err != nil {
b.Fatal(err)
}
cfg := &Config{Service: "svc"}
t := plugins.DefaultTriggerMode
if err := cfg.validateAndInjectDefaults([]string{"svc"}, nil, &t); err != nil {
b.Fatal(err)
}
plugin := New(cfg, manager)
var event EventV1
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
b.Fatal(err)
}
input, err := event.AST()
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := plugin.maskEvent(ctx, nil, input, &event); err != nil {
b.Fatal(err)
}
if event.Input != nil {
b.Fatal("Expected input to be erased")
}
}
}
File diff suppressed because it is too large Load Diff
-58
View File
@@ -1,58 +0,0 @@
// Copyright 2023 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package status
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"github.com/open-policy-agent/opa/metrics"
)
const (
errCode = "decision_log_error"
)
// Status represents the status of processing a decision log.
type Status struct {
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
HTTPCode json.Number `json:"http_code,omitempty"`
Metrics metrics.Metrics `json:"metrics,omitempty"`
}
// SetError updates the status object to reflect a failure to upload or
// process a log. If err is nil, the error status is cleared.
func (s *Status) SetError(err error) {
var httpError HTTPError
switch {
case err == nil:
s.Code = ""
s.HTTPCode = ""
s.Message = ""
case errors.As(err, &httpError):
s.Code = errCode
s.HTTPCode = json.Number(strconv.Itoa(httpError.StatusCode))
s.Message = err.Error()
default:
s.Code = errCode
s.HTTPCode = ""
s.Message = err.Error()
}
}
type HTTPError struct {
StatusCode int
}
func (e HTTPError) Error() string {
return fmt.Sprintf("log upload failed, server replied with HTTP %v %v", e.StatusCode, http.StatusText(e.StatusCode))
}