mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-16 13:22:53 -06:00
ff900144a7
The log plugin will report its status as "ok" at start time and "not ready" at stop. It currently doesn't change status or report any errors. This can potentially be extended in the future. Signed-off-by: Patrick East <east.patrick@gmail.com>
1052 lines
24 KiB
Go
1052 lines
24 KiB
Go
// 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 (
|
|
"compress/gzip"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/open-policy-agent/opa/ast"
|
|
"github.com/open-policy-agent/opa/metrics"
|
|
"github.com/open-policy-agent/opa/plugins"
|
|
"github.com/open-policy-agent/opa/server"
|
|
"github.com/open-policy-agent/opa/storage"
|
|
"github.com/open-policy-agent/opa/storage/inmem"
|
|
"github.com/open-policy-agent/opa/util"
|
|
"github.com/open-policy-agent/opa/version"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
// call flag.Parse() here if TestMain uses flags
|
|
setVersion("XY.Z")
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
type testPlugin struct {
|
|
events []EventV1
|
|
}
|
|
|
|
func (p *testPlugin) Start(context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (p *testPlugin) Stop(context.Context) {
|
|
}
|
|
|
|
func (p *testPlugin) Reconfigure(context.Context, interface{}) {
|
|
}
|
|
|
|
func (p *testPlugin) Log(_ context.Context, event EventV1) error {
|
|
p.events = append(p.events, event)
|
|
return nil
|
|
}
|
|
|
|
func TestPluginCustomBackend(t *testing.T) {
|
|
ctx := context.Background()
|
|
manager, _ := plugins.New(nil, "test-instance-id", inmem.New())
|
|
|
|
backend := &testPlugin{}
|
|
manager.Register("test_plugin", backend)
|
|
|
|
config, err := ParseConfig([]byte(`{"plugin": "test_plugin"}`), nil, []string{"test_plugin"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
plugin := New(config, manager)
|
|
plugin.Log(ctx, &server.Info{Revision: "A"})
|
|
plugin.Log(ctx, &server.Info{Revision: "B"})
|
|
|
|
if len(backend.events) != 2 || backend.events[0].Revision != "A" || backend.events[1].Revision != "B" {
|
|
t.Fatal("Unexpected events:", backend.events)
|
|
}
|
|
|
|
// Server events with only `Revision` should not include bundles in the EventV1 struct
|
|
for _, e := range backend.events {
|
|
if len(e.Bundles) > 0 {
|
|
t.Errorf("Unexpected `bundles` in event")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPluginSingleBundle(t *testing.T) {
|
|
ctx := context.Background()
|
|
manager, _ := plugins.New(nil, "test-instance-id", inmem.New())
|
|
|
|
backend := &testPlugin{}
|
|
manager.Register("test_plugin", backend)
|
|
|
|
config, err := ParseConfig([]byte(`{"plugin": "test_plugin"}`), nil, []string{"test_plugin"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
plugin := New(config, manager)
|
|
plugin.Log(ctx, &server.Info{Bundles: map[string]server.BundleInfo{"b1": {Revision: "A"}}})
|
|
|
|
// Server events with `Bundles` should *not* have `Revision` set
|
|
if len(backend.events) != 1 {
|
|
t.Fatalf("Unexpected number of events: %v", backend.events)
|
|
}
|
|
|
|
if backend.events[0].Revision != "" || backend.events[0].Bundles["b1"].Revision != "A" {
|
|
t.Fatal("Unexpected events: ", backend.events)
|
|
}
|
|
}
|
|
|
|
func TestPluginMultiBundle(t *testing.T) {
|
|
|
|
}
|
|
|
|
func TestPluginErrorNoResult(t *testing.T) {
|
|
ctx := context.Background()
|
|
manager, _ := plugins.New(nil, "test-instance-id", inmem.New())
|
|
|
|
backend := &testPlugin{}
|
|
manager.Register("test_plugin", backend)
|
|
|
|
config, err := ParseConfig([]byte(`{"plugin": "test_plugin"}`), nil, []string{"test_plugin"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
plugin := New(config, manager)
|
|
plugin.Log(ctx, &server.Info{Error: fmt.Errorf("some error")})
|
|
plugin.Log(ctx, &server.Info{Error: ast.Errors{&ast.Error{
|
|
Code: "some_error",
|
|
}}})
|
|
|
|
if len(backend.events) != 2 || backend.events[0].Error == nil || backend.events[1].Error == nil {
|
|
t.Fatal("Unexpected events:", backend.events)
|
|
}
|
|
}
|
|
|
|
func TestPluginQueriesAndPaths(t *testing.T) {
|
|
ctx := context.Background()
|
|
manager, _ := plugins.New(nil, "test-instance-id", inmem.New())
|
|
|
|
backend := &testPlugin{}
|
|
manager.Register("test_plugin", backend)
|
|
|
|
config, err := ParseConfig([]byte(`{"plugin": "test_plugin"}`), nil, []string{"test_plugin"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
plugin := New(config, manager)
|
|
plugin.Log(ctx, &server.Info{Path: "/"})
|
|
plugin.Log(ctx, &server.Info{Path: "/data"}) // /v1/data/data case
|
|
plugin.Log(ctx, &server.Info{Path: "/foo"})
|
|
plugin.Log(ctx, &server.Info{Path: "foo"})
|
|
plugin.Log(ctx, &server.Info{Path: "/foo/bar"})
|
|
plugin.Log(ctx, &server.Info{Path: "a.b.c"})
|
|
plugin.Log(ctx, &server.Info{Path: "/foo/a.b.c/bar"})
|
|
plugin.Log(ctx, &server.Info{Query: "a = data.foo"})
|
|
|
|
exp := []struct {
|
|
query string
|
|
path string
|
|
}{
|
|
{path: "/"},
|
|
{path: "/data"},
|
|
{path: "/foo"},
|
|
{path: "foo"},
|
|
{path: "/foo/bar"},
|
|
{path: "a.b.c"},
|
|
{path: "/foo/a.b.c/bar"},
|
|
{query: "a = data.foo"},
|
|
}
|
|
|
|
if len(exp) != len(backend.events) {
|
|
t.Fatalf("Expected %d events but got %v", len(exp), len(backend.events))
|
|
}
|
|
|
|
for i, e := range exp {
|
|
if e.query != backend.events[i].Query || e.path != backend.events[i].Path {
|
|
t.Fatalf("Unexpected event %d, want %+v but got %+v", i, e, backend.events[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPluginStartSameInput(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
fixture := newTestFixture(t)
|
|
defer fixture.server.stop()
|
|
|
|
fixture.server.ch = make(chan []EventV1, 4)
|
|
var result interface{} = false
|
|
|
|
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
testMetrics := getWellKnownMetrics()
|
|
|
|
var input interface{} = map[string]interface{}{"method": "GET"}
|
|
|
|
for i := 0; i < 400; i++ {
|
|
fixture.plugin.Log(ctx, &server.Info{
|
|
Revision: fmt.Sprint(i),
|
|
DecisionID: fmt.Sprint(i),
|
|
Path: "tda/bar",
|
|
Input: &input,
|
|
Results: &result,
|
|
RemoteAddr: "test",
|
|
Timestamp: ts,
|
|
Metrics: testMetrics,
|
|
})
|
|
}
|
|
|
|
_, err = fixture.plugin.oneShot(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
chunk1 := <-fixture.server.ch
|
|
chunk2 := <-fixture.server.ch
|
|
chunk3 := <-fixture.server.ch
|
|
chunk4 := <-fixture.server.ch
|
|
expLen1 := 122
|
|
expLen2 := 121
|
|
expLen3 := 121
|
|
expLen4 := 36
|
|
|
|
if len(chunk1) != expLen1 || len(chunk2) != expLen2 || len(chunk3) != expLen3 || len(chunk4) != expLen4 {
|
|
t.Fatalf("Expected chunk lens %v, %v, %v and %v but got: %v, %v, %v and %v", expLen1, expLen2, expLen3, expLen4, len(chunk1), len(chunk2), len(chunk3), len(chunk4))
|
|
}
|
|
|
|
var expInput interface{} = map[string]interface{}{"method": "GET"}
|
|
|
|
msAsFloat64 := map[string]interface{}{}
|
|
for k, v := range testMetrics.All() {
|
|
msAsFloat64[k] = float64(v.(uint64))
|
|
}
|
|
|
|
exp := EventV1{
|
|
Labels: map[string]string{
|
|
"id": "test-instance-id",
|
|
"app": "example-app",
|
|
"version": getVersion(),
|
|
},
|
|
Revision: "399",
|
|
DecisionID: "399",
|
|
Path: "tda/bar",
|
|
Input: &expInput,
|
|
Result: &result,
|
|
RequestedBy: "test",
|
|
Timestamp: ts,
|
|
Metrics: msAsFloat64,
|
|
}
|
|
|
|
if !reflect.DeepEqual(chunk4[expLen4-1], exp) {
|
|
t.Fatalf("Expected %+v but got %+v", exp, chunk4[expLen4-1])
|
|
}
|
|
}
|
|
|
|
func TestPluginStartChangingInputValues(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
fixture := newTestFixture(t)
|
|
defer fixture.server.stop()
|
|
|
|
fixture.server.ch = make(chan []EventV1, 4)
|
|
var result interface{} = false
|
|
|
|
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var input interface{}
|
|
|
|
for i := 0; i < 400; i++ {
|
|
input = map[string]interface{}{"method": getValueForMethod(i), "path": getValueForPath(i), "user": getValueForUser(i)}
|
|
|
|
fixture.plugin.Log(ctx, &server.Info{
|
|
Revision: fmt.Sprint(i),
|
|
DecisionID: fmt.Sprint(i),
|
|
Path: "foo/bar",
|
|
Input: &input,
|
|
Results: &result,
|
|
RemoteAddr: "test",
|
|
Timestamp: ts,
|
|
})
|
|
}
|
|
|
|
_, err = fixture.plugin.oneShot(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
chunk1 := <-fixture.server.ch
|
|
chunk2 := <-fixture.server.ch
|
|
chunk3 := <-fixture.server.ch
|
|
chunk4 := <-fixture.server.ch
|
|
expLen1 := 124
|
|
expLen2 := 123
|
|
expLen3 := 123
|
|
expLen4 := 30
|
|
|
|
if len(chunk1) != expLen1 || len(chunk2) != expLen2 || len((chunk3)) != expLen3 || len(chunk4) != expLen4 {
|
|
t.Fatalf("Expected chunk lens %v, %v, %v and %v but got: %v, %v, %v and %v", expLen1, expLen2, expLen3, expLen4, len(chunk1), len(chunk2), len(chunk3), len(chunk4))
|
|
}
|
|
|
|
var expInput interface{} = input
|
|
|
|
exp := EventV1{
|
|
Labels: map[string]string{
|
|
"id": "test-instance-id",
|
|
"app": "example-app",
|
|
"version": getVersion(),
|
|
},
|
|
Revision: "399",
|
|
DecisionID: "399",
|
|
Path: "foo/bar",
|
|
Input: &expInput,
|
|
Result: &result,
|
|
RequestedBy: "test",
|
|
Timestamp: ts,
|
|
}
|
|
|
|
if !reflect.DeepEqual(chunk4[expLen4-1], exp) {
|
|
t.Fatalf("Expected %+v but got %+v", exp, chunk4[expLen4-1])
|
|
}
|
|
}
|
|
|
|
func TestPluginStartChangingInputKeysAndValues(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
fixture := newTestFixture(t)
|
|
defer fixture.server.stop()
|
|
|
|
fixture.server.ch = make(chan []EventV1, 5)
|
|
var result interface{} = false
|
|
|
|
ts, err := time.Parse(time.RFC3339Nano, "2018-01-01T12:00:00.123456Z")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
var input interface{}
|
|
|
|
for i := 0; i < 250; i++ {
|
|
input = generateInputMap(i)
|
|
|
|
fixture.plugin.Log(ctx, &server.Info{
|
|
Revision: fmt.Sprint(i),
|
|
DecisionID: fmt.Sprint(i),
|
|
Path: "foo/bar",
|
|
Input: &input,
|
|
Results: &result,
|
|
RemoteAddr: "test",
|
|
Timestamp: ts,
|
|
})
|
|
}
|
|
|
|
_, err = fixture.plugin.oneShot(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
<-fixture.server.ch
|
|
chunk2 := <-fixture.server.ch
|
|
|
|
var expInput interface{} = input
|
|
|
|
exp := EventV1{
|
|
Labels: map[string]string{
|
|
"id": "test-instance-id",
|
|
"app": "example-app",
|
|
"version": getVersion(),
|
|
},
|
|
Revision: "249",
|
|
DecisionID: "249",
|
|
Path: "foo/bar",
|
|
Input: &expInput,
|
|
Result: &result,
|
|
RequestedBy: "test",
|
|
Timestamp: ts,
|
|
}
|
|
|
|
if !reflect.DeepEqual(chunk2[len(chunk2)-1], exp) {
|
|
t.Fatalf("Expected %+v but got %+v", exp, chunk2[len(chunk2)-1])
|
|
}
|
|
}
|
|
|
|
func TestPluginRequeue(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
fixture := newTestFixture(t)
|
|
defer fixture.server.stop()
|
|
|
|
fixture.server.ch = make(chan []EventV1, 1)
|
|
|
|
var input interface{} = map[string]interface{}{"method": "GET"}
|
|
var result1 interface{} = false
|
|
|
|
fixture.plugin.Log(ctx, &server.Info{
|
|
DecisionID: "abc",
|
|
Path: "data.foo.bar",
|
|
Input: &input,
|
|
Results: &result1,
|
|
RemoteAddr: "test",
|
|
Timestamp: time.Now().UTC(),
|
|
})
|
|
|
|
fixture.server.expCode = 500
|
|
_, err := fixture.plugin.oneShot(ctx)
|
|
if err == nil {
|
|
t.Fatal("Expected error")
|
|
}
|
|
|
|
events1 := <-fixture.server.ch
|
|
|
|
fixture.server.expCode = 200
|
|
|
|
_, err = fixture.plugin.oneShot(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
events2 := <-fixture.server.ch
|
|
|
|
if !reflect.DeepEqual(events1, events2) {
|
|
t.Fatalf("Expected %v but got: %v", events1, events2)
|
|
}
|
|
|
|
uploaded, err := fixture.plugin.oneShot(ctx)
|
|
if uploaded || err != nil {
|
|
t.Fatalf("Unexpected error or upload, err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPluginReconfigure(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
fixture := newTestFixture(t)
|
|
defer fixture.server.stop()
|
|
|
|
if err := fixture.plugin.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ensurePluginState(t, fixture.plugin, plugins.StateOK)
|
|
|
|
minDelay := 2
|
|
maxDelay := 3
|
|
|
|
pluginConfig := []byte(fmt.Sprintf(`{
|
|
"service": "example",
|
|
"reporting": {
|
|
"min_delay_seconds": %v,
|
|
"max_delay_seconds": %v
|
|
}
|
|
}`, minDelay, maxDelay))
|
|
|
|
config, _ := ParseConfig(pluginConfig, fixture.manager.Services(), nil)
|
|
|
|
fixture.plugin.Reconfigure(ctx, config)
|
|
ensurePluginState(t, fixture.plugin, plugins.StateOK)
|
|
|
|
fixture.plugin.Stop(ctx)
|
|
ensurePluginState(t, fixture.plugin, plugins.StateNotReady)
|
|
|
|
actualMin := time.Duration(*fixture.plugin.config.Reporting.MinDelaySeconds) / time.Nanosecond
|
|
expectedMin := time.Duration(minDelay) * time.Second
|
|
|
|
if actualMin != expectedMin {
|
|
t.Fatalf("Expected minimum polling interval: %v but got %v", expectedMin, actualMin)
|
|
}
|
|
|
|
actualMax := time.Duration(*fixture.plugin.config.Reporting.MaxDelaySeconds) / time.Nanosecond
|
|
expectedMax := time.Duration(maxDelay) * time.Second
|
|
|
|
if actualMax != expectedMax {
|
|
t.Fatalf("Expected maximum polling interval: %v but got %v", expectedMax, actualMax)
|
|
}
|
|
}
|
|
|
|
func TestPluginMasking(t *testing.T) {
|
|
|
|
// Setup masking fixture. Populate store with simple masking policy.
|
|
ctx := context.Background()
|
|
store := inmem.New()
|
|
|
|
err := storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
if err := store.UpsertPolicy(ctx, txn, "test.rego", []byte(`
|
|
package system.log
|
|
mask["/input/password"] {
|
|
input.input.is_sensitive
|
|
}
|
|
`)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create and start manager. Start is required so that stored policies
|
|
// get compiled and made available to the plugin.
|
|
manager, err := plugins.New(nil, "test", store)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
} else if err := manager.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Instantiate the plugin.
|
|
cfg := &Config{Service: "svc"}
|
|
cfg.validateAndInjectDefaults([]string{"svc"}, nil)
|
|
plugin := New(cfg, manager)
|
|
|
|
if err := plugin.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Test input that requires masking.
|
|
var input interface{} = map[string]interface{}{
|
|
"is_sensitive": true,
|
|
"password": "secret",
|
|
}
|
|
event := &EventV1{
|
|
Input: &input,
|
|
}
|
|
if err := plugin.maskEvent(ctx, nil, event); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var exp interface{} = map[string]interface{}{
|
|
"is_sensitive": true,
|
|
}
|
|
|
|
if !reflect.DeepEqual(exp, *event.Input) {
|
|
t.Fatalf("Expected %v but got %v:", exp, *event.Input)
|
|
}
|
|
|
|
expErased := []string{"/input/password"}
|
|
|
|
if !reflect.DeepEqual(expErased, event.Erased) {
|
|
t.Fatalf("Expected %v but got %v:", expErased, event.Erased)
|
|
}
|
|
|
|
// Test input that DOES NOT require masking.
|
|
input = map[string]interface{}{
|
|
"password": "secret", // is_sensitive not set.
|
|
}
|
|
|
|
event = &EventV1{
|
|
Input: &input,
|
|
}
|
|
|
|
if err := plugin.maskEvent(ctx, nil, event); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
exp = map[string]interface{}{
|
|
"password": "secret",
|
|
}
|
|
|
|
if !reflect.DeepEqual(exp, *event.Input) {
|
|
t.Fatalf("Expected %v but got %v:", exp, *event.Input)
|
|
}
|
|
|
|
if len(event.Erased) != 0 {
|
|
t.Fatalf("Expected empty set but got %v", event.Erased)
|
|
}
|
|
|
|
// Update policy to mask all of input and exercise.
|
|
err = storage.Txn(ctx, store, storage.WriteParams, func(txn storage.Transaction) error {
|
|
if err := store.UpsertPolicy(ctx, txn, "test.rego", []byte(`
|
|
package system.log
|
|
mask["/input"]
|
|
`)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
event = &EventV1{
|
|
Input: &input,
|
|
}
|
|
|
|
if err := plugin.maskEvent(ctx, nil, event); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if event.Input != nil {
|
|
t.Fatalf("Expected input to be nil but got: %v", *event.Input)
|
|
}
|
|
|
|
// Reconfigure and ensure that mask is invalidated.
|
|
maskDecision := "dead/beef"
|
|
newConfig := &Config{Service: "svc", MaskDecision: &maskDecision}
|
|
if err := newConfig.validateAndInjectDefaults([]string{"svc"}, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
plugin.Reconfigure(ctx, newConfig)
|
|
|
|
input = map[string]interface{}{
|
|
"password": "secret",
|
|
"is_sensitive": true,
|
|
}
|
|
|
|
event = &EventV1{
|
|
Input: &input,
|
|
}
|
|
|
|
if err := plugin.maskEvent(ctx, nil, event); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
exp = map[string]interface{}{
|
|
"password": "secret",
|
|
"is_sensitive": true,
|
|
}
|
|
|
|
if !reflect.DeepEqual(exp, input) {
|
|
t.Fatalf("Expected %v but got modified input %v", exp, input)
|
|
}
|
|
|
|
}
|
|
|
|
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": {
|
|
"reason": "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"}
|
|
cfg.validateAndInjectDefaults([]string{"svc"}, nil)
|
|
plugin := New(cfg, manager)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
b.StopTimer()
|
|
|
|
var event EventV1
|
|
|
|
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.StartTimer()
|
|
|
|
if err := plugin.maskEvent(ctx, nil, &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 {
|
|
if err := store.UpsertPolicy(ctx, txn, "test.rego", []byte(`
|
|
package system.log
|
|
|
|
mask["/input"] {
|
|
input.input.request.kind.kind == "Pod"
|
|
}
|
|
`)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
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"}
|
|
cfg.validateAndInjectDefaults([]string{"svc"}, nil)
|
|
plugin := New(cfg, manager)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
b.StopTimer()
|
|
|
|
var event EventV1
|
|
|
|
if err := util.UnmarshalJSON([]byte(largeEvent), &event); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
b.StartTimer()
|
|
|
|
if err := plugin.maskEvent(ctx, nil, &event); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
if event.Input != nil {
|
|
b.Fatal("Expected input to be erased")
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
type testFixture struct {
|
|
manager *plugins.Manager
|
|
plugin *Plugin
|
|
server *testServer
|
|
}
|
|
|
|
func newTestFixture(t *testing.T) testFixture {
|
|
|
|
ts := testServer{
|
|
t: t,
|
|
expCode: 200,
|
|
}
|
|
|
|
ts.start()
|
|
|
|
managerConfig := []byte(fmt.Sprintf(`{
|
|
"labels": {
|
|
"app": "example-app"
|
|
},
|
|
"services": [
|
|
{
|
|
"name": "example",
|
|
"url": %q,
|
|
"credentials": {
|
|
"bearer": {
|
|
"scheme": "Bearer",
|
|
"token": "secret"
|
|
}
|
|
}
|
|
}
|
|
]}`, ts.server.URL))
|
|
|
|
manager, err := plugins.New(managerConfig, "test-instance-id", inmem.New())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
pluginConfig := []byte(fmt.Sprintf(`{
|
|
"service": "example",
|
|
}`))
|
|
|
|
config, _ := ParseConfig([]byte(pluginConfig), manager.Services(), nil)
|
|
|
|
if s, ok := manager.PluginStatus()[Name]; ok {
|
|
t.Fatalf("Unexpected status found in plugin manager for %s: %+v", Name, s)
|
|
}
|
|
|
|
p := New(config, manager)
|
|
|
|
ensurePluginState(t, p, plugins.StateNotReady)
|
|
|
|
return testFixture{
|
|
manager: manager,
|
|
plugin: p,
|
|
server: &ts,
|
|
}
|
|
|
|
}
|
|
|
|
func TestParseConfigUseDefaultServiceNoConsole(t *testing.T) {
|
|
services := []string{
|
|
"s0",
|
|
"s1",
|
|
"s3",
|
|
}
|
|
|
|
loggerConfig := []byte(fmt.Sprintf(`{
|
|
"console": false
|
|
}`))
|
|
|
|
config, err := ParseConfig([]byte(loggerConfig), services, nil)
|
|
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %s", err)
|
|
}
|
|
|
|
if config.Service != services[0] {
|
|
t.Errorf("Expected %s service in config, actual = '%s'", services[0], config.Service)
|
|
}
|
|
}
|
|
|
|
func TestParseConfigDefaultServiceWithConsole(t *testing.T) {
|
|
services := []string{
|
|
"s0",
|
|
"s1",
|
|
"s3",
|
|
}
|
|
|
|
loggerConfig := []byte(fmt.Sprintf(`{
|
|
"console": true
|
|
}`))
|
|
|
|
config, err := ParseConfig([]byte(loggerConfig), services, nil)
|
|
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %s", err)
|
|
}
|
|
|
|
if config.Service != "" {
|
|
t.Errorf("Expected no service in config, actual = '%s'", config.Service)
|
|
}
|
|
}
|
|
|
|
func TestParseConfigDefaultServiceWithNoServiceOrConsole(t *testing.T) {
|
|
loggerConfig := []byte(fmt.Sprintf(`{}`))
|
|
|
|
_, err := ParseConfig([]byte(loggerConfig), []string{}, nil)
|
|
|
|
if err == nil {
|
|
t.Errorf("Expected an error but err==nil")
|
|
}
|
|
}
|
|
|
|
type testServer struct {
|
|
t *testing.T
|
|
expCode int
|
|
server *httptest.Server
|
|
ch chan []EventV1
|
|
}
|
|
|
|
func (t *testServer) handle(w http.ResponseWriter, r *http.Request) {
|
|
gr, err := gzip.NewReader(r.Body)
|
|
if err != nil {
|
|
t.t.Fatal(err)
|
|
}
|
|
var events []EventV1
|
|
if err := json.NewDecoder(gr).Decode(&events); err != nil {
|
|
t.t.Fatal(err)
|
|
}
|
|
if err := gr.Close(); err != nil {
|
|
t.t.Fatal(err)
|
|
}
|
|
t.t.Logf("decision log test server received %d events", len(events))
|
|
t.ch <- events
|
|
w.WriteHeader(t.expCode)
|
|
}
|
|
|
|
func (t *testServer) start() {
|
|
t.server = httptest.NewServer(http.HandlerFunc(t.handle))
|
|
}
|
|
|
|
func (t *testServer) stop() {
|
|
t.server.Close()
|
|
}
|
|
|
|
func getValueForMethod(idx int) string {
|
|
methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH"}
|
|
return methods[idx%len(methods)]
|
|
}
|
|
|
|
func getValueForPath(idx int) string {
|
|
paths := []string{"/blah1", "/blah2", "/blah3", "/blah4"}
|
|
return paths[idx%len(paths)]
|
|
}
|
|
|
|
func getValueForUser(idx int) string {
|
|
users := []string{"Alice", "Bob", "Charlie", "David", "Ed"}
|
|
return users[idx%len(users)]
|
|
}
|
|
|
|
func generateInputMap(idx int) map[string]interface{} {
|
|
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
result := make(map[string]interface{})
|
|
|
|
for i := 0; i < 20; i++ {
|
|
n := idx % len(letters)
|
|
key := string(letters[n])
|
|
result[key] = fmt.Sprint(idx)
|
|
}
|
|
return result
|
|
|
|
}
|
|
|
|
func setVersion(opaVersion string) {
|
|
if version.Version == "" {
|
|
version.Version = opaVersion
|
|
}
|
|
}
|
|
|
|
func getVersion() string {
|
|
return version.Version
|
|
}
|
|
|
|
func getWellKnownMetrics() metrics.Metrics {
|
|
m := metrics.New()
|
|
m.Counter("test_counter").Incr()
|
|
return m
|
|
}
|
|
|
|
func ensurePluginState(t *testing.T, p *Plugin, state plugins.State) {
|
|
t.Helper()
|
|
status, ok := p.manager.PluginStatus()[Name]
|
|
if !ok {
|
|
t.Fatalf("Expected to find state for %s, found nil", Name)
|
|
return
|
|
}
|
|
if status.State != state {
|
|
t.Fatalf("Unexpected status state found in plugin manager for %s:\n\n\tFound:%+v\n\n\tExpected: %s", Name, status.State, plugins.StateOK)
|
|
}
|
|
}
|