* Have hclog payloads use `json` tags
Previously a global setting for the `structs` lib was causing the `json`
tag to be used, but this could have side effects from other packages, or
could be affected by other packages' behavior. This behavior was removed
earlier but it means this format call no longer uses `json` tags; this
behavior reverts it.
It also normalizes using mapstructure in a few other places.
* Update some tests
* Fix incorrect op const
* Fix more tests
* refactor (events): tell filter to use json tags (fix related tests)
Explicitly tell the filter to use the "json" tags so we don't have to
re-tag everything with a mapstructure tag via bexpr.WithTagName("json")
Update docs
Co-authored-by: Jim <jlambert@hashicorp.com>
438 lines
9.8 KiB
Go
438 lines
9.8 KiB
Go
package event
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/eventlogger"
|
|
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestHclogFormatter_Process(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
|
|
f, e := newFilter(`op == "match-filter"`)
|
|
require.NoError(t, e)
|
|
|
|
testPredicate := newPredicate([]*filter{f}, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
formatter *hclogFormatterFilter
|
|
e *eventlogger.Event
|
|
wantErrContains string
|
|
want []string
|
|
}{
|
|
{
|
|
name: "nil event",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: false,
|
|
},
|
|
wantErrContains: "event is nil",
|
|
},
|
|
{
|
|
name: "invalid-event-type",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: false,
|
|
},
|
|
e: &eventlogger.Event{Type: eventlogger.EventType("invalid-type")},
|
|
wantErrContains: "unknown event type invalid-type",
|
|
},
|
|
{
|
|
name: "sys-text",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: false,
|
|
},
|
|
e: &eventlogger.Event{
|
|
Type: eventlogger.EventType(SystemType),
|
|
Payload: &sysEvent{
|
|
Id: "1",
|
|
Version: errorVersion,
|
|
Op: Op("text"),
|
|
Data: map[string]interface{}{
|
|
"msg": "hello",
|
|
},
|
|
},
|
|
},
|
|
want: []string{
|
|
"[INFO] system event:",
|
|
"data:msg=hello",
|
|
"version=v0.1",
|
|
"op=text",
|
|
},
|
|
},
|
|
{
|
|
name: "observation-text",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: false,
|
|
},
|
|
e: &eventlogger.Event{
|
|
Type: eventlogger.EventType(ObservationType),
|
|
Payload: map[string]interface{}{
|
|
"id": "1",
|
|
"version": observationVersion,
|
|
"latency-ms": 10,
|
|
},
|
|
},
|
|
want: []string{
|
|
"[INFO] observation event:",
|
|
"latency-ms=10",
|
|
"id=1",
|
|
"version=v0.1",
|
|
},
|
|
},
|
|
{
|
|
name: "observation-json",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: true,
|
|
},
|
|
e: &eventlogger.Event{
|
|
Type: eventlogger.EventType(ObservationType),
|
|
Payload: map[string]interface{}{
|
|
"id": "1",
|
|
"version": observationVersion,
|
|
"latency-ms": 10,
|
|
},
|
|
},
|
|
want: []string{
|
|
"{\"@level\":\"info\",\"@message\":\"observation event\"",
|
|
"\"latency-ms\":10",
|
|
"\"id\":\"1\"",
|
|
"\"version\":\"v0.1\"}\n",
|
|
},
|
|
},
|
|
{
|
|
name: "err-text",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: false,
|
|
},
|
|
e: &eventlogger.Event{
|
|
Type: eventlogger.EventType(ErrorType),
|
|
Payload: &err{
|
|
Id: "1",
|
|
Version: errorVersion,
|
|
Error: ErrInvalidParameter.Error(),
|
|
Op: Op("text"),
|
|
},
|
|
},
|
|
want: []string{
|
|
"[ERROR] error event:",
|
|
"error=\"invalid parameter\"",
|
|
"id=1",
|
|
"version=v0.1",
|
|
"op=text",
|
|
},
|
|
},
|
|
{
|
|
name: "err-json",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: true,
|
|
},
|
|
e: &eventlogger.Event{
|
|
Type: eventlogger.EventType(ErrorType),
|
|
Payload: &err{
|
|
Id: "1",
|
|
Version: errorVersion,
|
|
Error: ErrInvalidParameter.Error(),
|
|
Op: Op("text"),
|
|
},
|
|
},
|
|
want: []string{
|
|
"{\"@level\":\"error\",\"@message\":\"error event\"",
|
|
"\"error\":\"invalid parameter\"",
|
|
"\"id\":\"1\"",
|
|
"\"version\":\"v0.1\"",
|
|
"\"op\":\"text\"",
|
|
},
|
|
},
|
|
{
|
|
name: "err-text-with-optional",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: false,
|
|
},
|
|
e: &eventlogger.Event{
|
|
Type: eventlogger.EventType(ErrorType),
|
|
Payload: &err{
|
|
Id: "1",
|
|
Version: errorVersion,
|
|
Error: ErrInvalidParameter.Error(),
|
|
Op: Op("text"),
|
|
Info: map[string]interface{}{"name": "alice"},
|
|
},
|
|
},
|
|
want: []string{
|
|
"[ERROR] error event:",
|
|
"error=\"invalid parameter\"",
|
|
"id=1",
|
|
"version=v0.1",
|
|
"info:name=alice",
|
|
"op=text",
|
|
},
|
|
},
|
|
{
|
|
name: "filter-match",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: false,
|
|
predicate: testPredicate,
|
|
},
|
|
e: &eventlogger.Event{
|
|
Type: eventlogger.EventType(SystemType),
|
|
Payload: &sysEvent{
|
|
Id: "1",
|
|
Version: errorVersion,
|
|
Op: Op("match-filter"),
|
|
Data: map[string]interface{}{
|
|
"msg": "hello",
|
|
},
|
|
},
|
|
},
|
|
want: []string{
|
|
"[INFO] system event:",
|
|
"data:msg=hello",
|
|
"version=v0.1",
|
|
"op=match-filter",
|
|
},
|
|
},
|
|
{
|
|
name: "filter-no-match",
|
|
formatter: &hclogFormatterFilter{
|
|
jsonFormat: false,
|
|
predicate: testPredicate,
|
|
},
|
|
e: &eventlogger.Event{
|
|
Type: eventlogger.EventType(SystemType),
|
|
Payload: &sysEvent{
|
|
Id: "1",
|
|
Version: errorVersion,
|
|
Op: Op("doesn't match"),
|
|
Data: map[string]interface{}{
|
|
"msg": "hello",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
e, err := tt.formatter.Process(ctx, tt.e)
|
|
if tt.wantErrContains != "" {
|
|
require.Error(err)
|
|
assert.Contains(err.Error(), tt.wantErrContains)
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
if len(tt.want) == 0 {
|
|
assert.Nil(e)
|
|
return
|
|
}
|
|
assert.NotNil(e)
|
|
var b []byte
|
|
var ok bool
|
|
switch tt.formatter.jsonFormat {
|
|
case true:
|
|
b, ok = e.Format(string(JSONHclogSinkFormat))
|
|
case false:
|
|
b, ok = e.Format(string(TextHclogSinkFormat))
|
|
}
|
|
t.Log(string(b))
|
|
require.True(ok)
|
|
for _, txt := range tt.want {
|
|
assert.Contains(string(b), txt)
|
|
}
|
|
})
|
|
}
|
|
t.Run("with-signing", func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
wrapper := testWrapper(t)
|
|
f, err := newHclogFormatterFilter(true) // produce json formatted events
|
|
require.NoError(err)
|
|
require.NoError(f.Rotate(wrapper))
|
|
require.NotNil(f.signer)
|
|
|
|
e := &eventlogger.Event{
|
|
Type: eventlogger.EventType(AuditType),
|
|
Payload: &audit{
|
|
Id: "1",
|
|
Version: auditVersion,
|
|
Auth: &Auth{UserName: "alice"},
|
|
},
|
|
}
|
|
|
|
gotEvent, err := f.Process(ctx, e)
|
|
require.NoError(err)
|
|
b, ok := gotEvent.Format(string(JSONHclogSinkFormat))
|
|
require.True(ok)
|
|
var rep map[string]interface{}
|
|
require.NoError(json.Unmarshal(b, &rep))
|
|
assert.NotEmpty(rep["serialized"])
|
|
assert.NotEmpty(rep["serialized_hmac"])
|
|
})
|
|
}
|
|
|
|
func Test_hclogFormatterFilter_Name(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("simple", func(t *testing.T) {
|
|
ff := &hclogFormatterFilter{}
|
|
assert.Equal(t, hclogNodeName, ff.Name())
|
|
})
|
|
}
|
|
|
|
func Test_hclogFormatterFilter_Reopen(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("simple", func(t *testing.T) {
|
|
ff := &hclogFormatterFilter{}
|
|
assert.Equal(t, nil, ff.Reopen())
|
|
})
|
|
}
|
|
|
|
func Test_hclogFormatterFilter_Type(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("simple", func(t *testing.T) {
|
|
ff := &hclogFormatterFilter{}
|
|
assert.Equal(t, eventlogger.NodeTypeFormatterFilter, ff.Type())
|
|
})
|
|
}
|
|
|
|
func Test_newHclogFormatterFilter(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
jsonFormat bool
|
|
opt []Option
|
|
wantErr bool
|
|
wantIsError error
|
|
wantErrContains string
|
|
wantAllow []string
|
|
wantDeny []string
|
|
}{
|
|
{
|
|
name: "no-opts",
|
|
},
|
|
{
|
|
name: "bad-allow-filter",
|
|
jsonFormat: true,
|
|
opt: []Option{
|
|
WithAllow("foo=;22", "foo==bar"),
|
|
},
|
|
wantErr: true,
|
|
wantErrContains: "invalid allow filter 'foo=;22'",
|
|
},
|
|
{
|
|
name: "bad-deny-filter",
|
|
jsonFormat: true,
|
|
opt: []Option{
|
|
WithDeny("foo=;22", "foo==bar"),
|
|
},
|
|
wantErr: true,
|
|
wantErrContains: "invalid deny filter 'foo=;22'",
|
|
},
|
|
{
|
|
name: "empty-allow-filter",
|
|
jsonFormat: true,
|
|
opt: []Option{
|
|
WithAllow(""),
|
|
},
|
|
wantErr: true,
|
|
wantErrContains: "missing filter",
|
|
},
|
|
{
|
|
name: "empty-deny-filter",
|
|
jsonFormat: true,
|
|
opt: []Option{
|
|
WithDeny(""),
|
|
},
|
|
wantErr: true,
|
|
wantErrContains: "missing filter",
|
|
},
|
|
{
|
|
name: "valid-filters",
|
|
jsonFormat: true,
|
|
opt: []Option{
|
|
WithAllow("alice==friend", "bob==friend"),
|
|
WithDeny("eve==acquaintance", "fido!=dog"),
|
|
},
|
|
wantAllow: []string{"alice==friend", "bob==friend"},
|
|
wantDeny: []string{"eve==acquaintance", "fido!=dog"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
got, err := newHclogFormatterFilter(tt.jsonFormat, tt.opt...)
|
|
if tt.wantErr {
|
|
require.Error(err)
|
|
assert.Nil(got)
|
|
if tt.wantIsError != nil {
|
|
assert.ErrorIs(err, tt.wantIsError)
|
|
}
|
|
if tt.wantErrContains != "" {
|
|
assert.Contains(err.Error(), tt.wantErrContains)
|
|
}
|
|
return
|
|
}
|
|
require.NoError(err)
|
|
assert.NotNil(got)
|
|
|
|
assert.Equal(tt.jsonFormat, got.jsonFormat)
|
|
|
|
assert.Len(got.allow, len(tt.wantAllow))
|
|
for _, f := range got.allow {
|
|
assert.Contains(tt.wantAllow, f.raw)
|
|
}
|
|
assert.Len(got.deny, len(tt.wantDeny)+1) // +1 since there's always a default deny
|
|
defs, err := defaultHclogEventsDenyFilters()
|
|
require.NoError(err)
|
|
for _, f := range defs {
|
|
tt.wantDeny = append(tt.wantDeny, f.raw)
|
|
}
|
|
for _, f := range got.deny {
|
|
assert.Contains(tt.wantDeny, f.raw)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_hclogFormatterFilter_Rotate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
f *hclogFormatterFilter
|
|
w wrapping.Wrapper
|
|
wantIsError error
|
|
wantErrContains string
|
|
}{
|
|
{
|
|
name: "missing-wrapper",
|
|
f: &hclogFormatterFilter{},
|
|
wantIsError: ErrInvalidParameter,
|
|
wantErrContains: "missing wrapper",
|
|
},
|
|
{
|
|
name: "valid",
|
|
f: &hclogFormatterFilter{},
|
|
w: testWrapper(t),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
err := tt.f.Rotate(tt.w)
|
|
if tt.wantIsError != nil {
|
|
require.Error(err)
|
|
assert.ErrorIs(err, tt.wantIsError)
|
|
if tt.wantErrContains != "" {
|
|
assert.Contains(err.Error(), tt.wantErrContains)
|
|
}
|
|
return
|
|
}
|
|
assert.NotNil(tt.f.signer)
|
|
})
|
|
}
|
|
}
|