feature: Add signing (hmac-sha256) of audit entries (#1666)
This commit is contained in:
16 files changed
+344
-37
No files matched your search
@@ -25,7 +25,7 @@ require (
|
||||
github.com/hashicorp/cap v0.1.1
|
||||
github.com/hashicorp/dawdle v0.4.0
|
||||
github.com/hashicorp/dbassert v0.0.0-20210708202608-ecf920cf1ed8
|
||||
github.com/hashicorp/eventlogger v0.1.0
|
||||
github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239
|
||||
github.com/hashicorp/go-bexpr v0.1.10
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
|
||||
@@ -456,8 +456,9 @@ github.com/hashicorp/dbassert v0.0.0-20210708202608-ecf920cf1ed8/go.mod h1:+B5eZ
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/eventlogger v0.1.0 h1:S6xc4gZVzewuDUP4R4Ngko419h/CGDuV/b4ADL3XLik=
|
||||
github.com/hashicorp/eventlogger v0.1.0/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0=
|
||||
github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144 h1:PCl0HtlVnIIloIozAKvjICu6K4IghKXAKNny3R3b2nI=
|
||||
github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 h1:Yh9tY0lige+y0trmjQeT9NRDo6+YvtNAzbmUNOsIUzI=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
|
||||
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package base
|
||||
|
||||
import "github.com/hashicorp/boundary/internal/observability/event"
|
||||
import (
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
)
|
||||
|
||||
// getOpts - iterate the inbound Options and return a struct.
|
||||
func getOpts(opt ...Option) Options {
|
||||
@@ -31,6 +34,7 @@ type Options struct {
|
||||
withDatabaseTemplate string
|
||||
withEventerConfig *event.EventerConfig
|
||||
withEventFlags *EventFlags
|
||||
withEventWrapper wrapping.Wrapper
|
||||
withAttributeFieldPrefix string
|
||||
withStatusCode int
|
||||
}
|
||||
@@ -136,6 +140,12 @@ func WithEventFlags(flags *EventFlags) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithEventAuditWrapper(w wrapping.Wrapper) Option {
|
||||
return func(o *Options) {
|
||||
o.withEventWrapper = w
|
||||
}
|
||||
}
|
||||
|
||||
// WithAttributeFieldPrefix tells the command what prefix
|
||||
// to attach to attribute fields when they are returned as errors.
|
||||
func WithAttributeFieldPrefix(p string) Option {
|
||||
|
||||
@@ -192,7 +192,7 @@ func (b *Server) SetupEventing(logger hclog.Logger, serializationLock *sync.Mute
|
||||
}
|
||||
}
|
||||
|
||||
e, err := event.NewEventer(logger, serializationLock, serverName, *opts.withEventerConfig)
|
||||
e, err := event.NewEventer(logger, serializationLock, serverName, *opts.withEventerConfig, event.WithAuditWrapper(opts.withEventWrapper))
|
||||
if err != nil {
|
||||
return berrors.WrapDeprecated(err, op, berrors.WithMsg("unable to create eventer"))
|
||||
}
|
||||
|
||||
@@ -490,10 +490,6 @@ func (c *Command) Run(args []string) int {
|
||||
c.UI.Error(err.Error())
|
||||
return base.CommandUserError
|
||||
}
|
||||
if err := c.SetupEventing(c.Logger, c.StderrLock, serverName, base.WithEventerConfig(c.Config.Eventing), base.WithEventFlags(eventFlags)); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return base.CommandCliError
|
||||
}
|
||||
|
||||
// Initialize status grace period (0 denotes using env or default
|
||||
// here)
|
||||
@@ -512,6 +508,15 @@ func (c *Command) Run(args []string) int {
|
||||
c.UI.Error("Controller KMS not found after parsing KMS blocks")
|
||||
return base.CommandUserError
|
||||
}
|
||||
if err := c.SetupEventing(
|
||||
c.Logger,
|
||||
c.StderrLock,
|
||||
serverName,
|
||||
base.WithEventerConfig(c.Config.Eventing),
|
||||
base.WithEventFlags(eventFlags)); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return base.CommandCliError
|
||||
}
|
||||
if c.WorkerAuthKms == nil {
|
||||
c.UI.Error("Worker Auth KMS not found after parsing KMS blocks")
|
||||
return base.CommandUserError
|
||||
|
||||
@@ -4,6 +4,4 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidParameter = errors.New("Invalid parameter")
|
||||
)
|
||||
var ErrInvalidParameter = errors.New("Invalid parameter")
|
||||
@@ -4,11 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
filterpkg "github.com/hashicorp/boundary/internal/filter"
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
|
||||
"github.com/hashicorp/go-bexpr"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
)
|
||||
|
||||
// cloudEventsFormatterFilter represents an eventlogger.cloudEventsFormatterFilter which filters events based on allow and
|
||||
@@ -17,6 +19,7 @@ type cloudEventsFormatterFilter struct {
|
||||
*cloudevents.FormatterFilter
|
||||
allow []*filter
|
||||
deny []*filter
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
// newCloudEventsFormatterFilter creates a new filter node using the optional allow and deny filters
|
||||
@@ -67,6 +70,23 @@ func newCloudEventsFormatterFilter(source *url.URL, format cloudevents.Format, o
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
// Rotate supports rotating the filter's wrapper. No options are currently
|
||||
// supported.
|
||||
func (f *cloudEventsFormatterFilter) Rotate(w wrapping.Wrapper, _ ...Option) error {
|
||||
const op = "event.(cloudEventsFormatterFilter).Rotate"
|
||||
if w == nil {
|
||||
return fmt.Errorf("%s: missing wrapper: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
f.l.Lock()
|
||||
defer f.l.Unlock()
|
||||
h, err := newSigner(context.Background(), w, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Signer = cloudevents.Signer(h)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newPredicate(allow, deny []*filter) func(ctx context.Context, ce interface{}) (bool, error) {
|
||||
return func(ctx context.Context, ce interface{}) (bool, error) {
|
||||
if len(allow) == 0 && len(deny) == 0 {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -343,3 +344,41 @@ func TestNode_Process(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_cloudEventsFormatter_Rotate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
f *cloudEventsFormatterFilter
|
||||
w wrapping.Wrapper
|
||||
wantIsError error
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "missing-wrapper",
|
||||
f: &cloudEventsFormatterFilter{FormatterFilter: &cloudevents.FormatterFilter{}},
|
||||
wantIsError: ErrInvalidParameter,
|
||||
wantErrContains: "missing wrapper",
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
f: &cloudEventsFormatterFilter{FormatterFilter: &cloudevents.FormatterFilter{}},
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
|
||||
"github.com/hashicorp/eventlogger/sinks/writer"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
)
|
||||
|
||||
@@ -67,6 +68,7 @@ type Eventer struct {
|
||||
auditPipelines []pipeline
|
||||
observationPipelines []pipeline
|
||||
errPipelines []pipeline
|
||||
auditWrapperNodes []interface{}
|
||||
}
|
||||
|
||||
type pipeline struct {
|
||||
@@ -187,9 +189,10 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
|
||||
}
|
||||
|
||||
e := &Eventer{
|
||||
logger: log,
|
||||
conf: c,
|
||||
broker: b,
|
||||
logger: log,
|
||||
conf: c,
|
||||
broker: b,
|
||||
auditWrapperNodes: []interface{}{},
|
||||
}
|
||||
|
||||
if !opts.withNow.IsZero() {
|
||||
@@ -208,7 +211,8 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
|
||||
allSinkFilenames := map[string]bool{}
|
||||
|
||||
for _, s := range c.Sinks {
|
||||
fmtId, fmtNode, err := newFmtFilterNode(serverName, *s)
|
||||
fmtId, fmtNode, err := newFmtFilterNode(serverName, *s, opt...)
|
||||
e.auditWrapperNodes = append(e.auditWrapperNodes, fmtNode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
@@ -287,6 +291,7 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
e.auditWrapperNodes = append(e.auditWrapperNodes, encryptFilter)
|
||||
if len(s.AuditConfig.FilterOverrides) > 0 {
|
||||
overrides := encrypt.DefaultFilterOperations()
|
||||
for k, v := range s.AuditConfig.FilterOverrides {
|
||||
@@ -464,11 +469,12 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func newFmtFilterNode(serverName string, c SinkConfig) (eventlogger.NodeID, eventlogger.Node, error) {
|
||||
func newFmtFilterNode(serverName string, c SinkConfig, opt ...Option) (eventlogger.NodeID, eventlogger.Node, error) {
|
||||
const op = "newFmtFilterNode"
|
||||
if serverName == "" {
|
||||
return "", nil, fmt.Errorf("%s: missing server name: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
var fmtId eventlogger.NodeID
|
||||
var fmtNode eventlogger.Node
|
||||
switch c.Format {
|
||||
@@ -479,7 +485,7 @@ func newFmtFilterNode(serverName string, c SinkConfig) (eventlogger.NodeID, even
|
||||
}
|
||||
fmtId = eventlogger.NodeID(id)
|
||||
|
||||
fmtNode, err = newHclogFormatterFilter(c.Format == JSONHclogSinkFormat, WithAllow(c.AllowFilters...), WithDeny(c.DenyFilters...))
|
||||
fmtNode, err = newHclogFormatterFilter(c.Format == JSONHclogSinkFormat, WithAllow(c.AllowFilters...), WithDeny(c.DenyFilters...), WithAuditWrapper(opts.withAuditWrapper))
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
@@ -531,6 +537,26 @@ func DefaultSink() *SinkConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Eventer) RotateAuditWrapper(ctx context.Context, newWrapper wrapping.Wrapper) error {
|
||||
const op = "event.(Eventer).RotateAuditWrapper"
|
||||
if newWrapper == nil {
|
||||
return fmt.Errorf("%s: missing wrapper: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
for _, n := range e.auditWrapperNodes {
|
||||
switch w := n.(type) {
|
||||
case *hclogFormatterFilter:
|
||||
w.Rotate(newWrapper)
|
||||
case *cloudEventsFormatterFilter:
|
||||
w.Rotate(newWrapper)
|
||||
case *encrypt.Filter:
|
||||
w.Rotate(encrypt.WithWrapper(newWrapper))
|
||||
default:
|
||||
return fmt.Errorf("%s: unsupported node type (%s): %w", op, reflect.TypeOf(w), ErrInvalidParameter)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeObservation writes/sends an Observation event.
|
||||
func (e *Eventer) writeObservation(ctx context.Context, event *observation) error {
|
||||
const op = "event.(Eventer).writeObservation"
|
||||
|
||||
@@ -13,8 +13,10 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/eventlogger/filters/encrypt"
|
||||
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -149,6 +151,7 @@ func Test_InitSysEventer(t *testing.T) {
|
||||
tt.want.auditPipelines = got.auditPipelines
|
||||
tt.want.errPipelines = got.errPipelines
|
||||
tt.want.observationPipelines = got.observationPipelines
|
||||
tt.want.auditWrapperNodes = got.auditWrapperNodes
|
||||
assert.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
@@ -716,6 +719,7 @@ func Test_NewEventer(t *testing.T) {
|
||||
tt.want.auditPipelines = got.auditPipelines
|
||||
tt.want.errPipelines = got.errPipelines
|
||||
tt.want.observationPipelines = got.observationPipelines
|
||||
tt.want.auditWrapperNodes = got.auditWrapperNodes
|
||||
assert.Equal(tt.want, got)
|
||||
|
||||
assert.Lenf(testBroker.registeredNodeIds, len(tt.wantRegistered), "got nodes: %q", testBroker.registeredNodeIds)
|
||||
@@ -1184,3 +1188,69 @@ func Test_logAdapter_Write(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventer_RotateAuditWrapper(t *testing.T) {
|
||||
// this test and its subtests cannot be run in parallel because of it's
|
||||
// dependency on the sysEventer
|
||||
|
||||
cloudEventsConfig := TestEventerConfig(t, "TestEventer_RotateAuditWrapper")
|
||||
hclogConfig := TestEventerConfig(t, "TestEventer_RotateAuditWrapper", testWithSinkFormat(t, JSONHclogSinkFormat))
|
||||
|
||||
testLock := &sync.Mutex{}
|
||||
testLogger := hclog.New(&hclog.LoggerOptions{
|
||||
Mutex: testLock,
|
||||
Name: "test",
|
||||
})
|
||||
|
||||
testCtx := context.Background()
|
||||
tests := []struct {
|
||||
name string
|
||||
w wrapping.Wrapper
|
||||
config TestConfig
|
||||
wantIsError error
|
||||
wantErrContains string
|
||||
}{
|
||||
{
|
||||
name: "missing-wrapper",
|
||||
config: cloudEventsConfig,
|
||||
wantIsError: ErrInvalidParameter,
|
||||
wantErrContains: "missing wrapper",
|
||||
},
|
||||
{
|
||||
name: "valid-cloudevents",
|
||||
w: testWrapper(t),
|
||||
config: cloudEventsConfig,
|
||||
},
|
||||
{
|
||||
name: "valid-hclog",
|
||||
w: testWrapper(t),
|
||||
config: hclogConfig,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
require.NoError(InitSysEventer(testLogger, testLock, "TestEventer_RotateAuditWrapper", WithEventerConfig(&tt.config.EventerConfig)))
|
||||
eventer := SysEventer()
|
||||
err := eventer.RotateAuditWrapper(testCtx, tt.w)
|
||||
if tt.wantIsError != nil {
|
||||
require.Error(err)
|
||||
assert.ErrorIs(err, tt.wantIsError)
|
||||
if tt.wantErrContains != "" {
|
||||
assert.Contains(err.Error(), tt.wantErrContains)
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, n := range eventer.auditWrapperNodes {
|
||||
switch w := n.(type) {
|
||||
case *hclogFormatterFilter:
|
||||
assert.NotNil(w.signer)
|
||||
case *cloudEventsFormatterFilter:
|
||||
assert.NotNil(w.Signer)
|
||||
case *encrypt.Filter:
|
||||
assert.NotNil(w.Wrapper)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,16 @@ package event
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,14 +31,18 @@ type hclogFormatterFilter struct {
|
||||
predicate func(ctx context.Context, i interface{}) (bool, error)
|
||||
allow []*filter
|
||||
deny []*filter
|
||||
signer signer
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
||||
func newHclogFormatterFilter(jsonFormat bool, opt ...Option) (*hclogFormatterFilter, error) {
|
||||
const op = "event.NewHclogFormatter"
|
||||
opts := getOpts(opt...)
|
||||
var s signer
|
||||
n := hclogFormatterFilter{
|
||||
jsonFormat: jsonFormat,
|
||||
signer: s,
|
||||
}
|
||||
opts := getOpts(opt...)
|
||||
// intentionally not checking if allow and/or deny optional filters were
|
||||
// supplied since having a filter node with no filters is okay.
|
||||
|
||||
@@ -64,6 +71,23 @@ func newHclogFormatterFilter(jsonFormat bool, opt ...Option) (*hclogFormatterFil
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
// Rotate supports rotating the filter's wrapper. No options are currently
|
||||
// supported.
|
||||
func (f *hclogFormatterFilter) Rotate(w wrapping.Wrapper, _ ...Option) error {
|
||||
const op = "event.(hclogFormatterFilter).Rotate"
|
||||
if w == nil {
|
||||
return fmt.Errorf("%s: missing wrapper: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
f.l.Lock()
|
||||
defer f.l.Unlock()
|
||||
h, err := newSigner(context.Background(), w, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.signer = h
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reopen is a no op
|
||||
func (_ *hclogFormatterFilter) Reopen() error { return nil }
|
||||
|
||||
@@ -154,23 +178,20 @@ func (f *hclogFormatterFilter) Process(ctx context.Context, e *eventlogger.Event
|
||||
args = append(args, k, v)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Output: &buf,
|
||||
Level: hclog.Trace,
|
||||
JSONFormat: f.jsonFormat,
|
||||
})
|
||||
const eventMarker = " event"
|
||||
switch string(e.Type) {
|
||||
case string(ErrorType):
|
||||
logger.Error(string(e.Type)+eventMarker, args...)
|
||||
case string(ObservationType), string(SystemType), string(AuditType):
|
||||
logger.Info(string(e.Type)+eventMarker, args...)
|
||||
default:
|
||||
// well, we should ever hit this, since we should be specific about the
|
||||
// event type we're processing, but adding this default to just be sure
|
||||
// we haven't missed anything.
|
||||
logger.Trace(string(e.Type)+eventMarker, args...)
|
||||
buf, err := hclogBytes(e.Type, f.jsonFormat, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to format: %w", op, err)
|
||||
}
|
||||
if f.signer != nil && string(e.Type) == string(AuditType) {
|
||||
bufHmac, err := f.signer(ctx, buf.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to hmac-sha256: %w", op, err)
|
||||
}
|
||||
args = append(args, "serialized", base64.RawURLEncoding.EncodeToString(buf.Bytes()), "serialized_hmac", bufHmac)
|
||||
buf, err = hclogBytes(e.Type, f.jsonFormat, args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to format after hmac-sha256: %w", op, err)
|
||||
}
|
||||
}
|
||||
switch f.jsonFormat {
|
||||
case true:
|
||||
@@ -181,3 +202,25 @@ func (f *hclogFormatterFilter) Process(ctx context.Context, e *eventlogger.Event
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func hclogBytes(eventType eventlogger.EventType, jsonFormat bool, args []interface{}) (*bytes.Buffer, error) {
|
||||
var buf bytes.Buffer
|
||||
logger := hclog.New(&hclog.LoggerOptions{
|
||||
Output: &buf,
|
||||
Level: hclog.Trace,
|
||||
JSONFormat: jsonFormat,
|
||||
})
|
||||
const eventMarker = " event"
|
||||
switch string(eventType) {
|
||||
case string(ErrorType):
|
||||
logger.Error(string(eventType)+eventMarker, args...)
|
||||
case string(ObservationType), string(SystemType), string(AuditType):
|
||||
logger.Info(string(eventType)+eventMarker, args...)
|
||||
default:
|
||||
// well, we should ever hit this, since we should be specific about the
|
||||
// event type we're processing, but adding this default to just be sure
|
||||
// we haven't missed anything.
|
||||
logger.Trace(string(eventType)+eventMarker, args...)
|
||||
}
|
||||
return &buf, nil
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/eventlogger"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -246,6 +248,32 @@ func TestHclogFormatter_Process(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
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) {
|
||||
@@ -366,3 +394,41 @@ func Test_newHclogFormatterFilter(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/libs/crypto"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
)
|
||||
|
||||
type signer func(context.Context, []byte) (string, error)
|
||||
|
||||
func newSigner(_ context.Context, w wrapping.Wrapper, info, salt []byte) (signer, error) {
|
||||
const op = "event.hmacHclogSigner"
|
||||
return func(requestCtx context.Context, data []byte) (string, error) {
|
||||
return crypto.HmacSha256(requestCtx, data, w, info, salt, crypto.WithPrefix("hmac-sha256:"), crypto.WithBase64Encoding())
|
||||
}, nil
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/session"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
"github.com/hashicorp/boundary/internal/types/scope"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-secure-stdlib/mlock"
|
||||
ua "go.uber.org/atomic"
|
||||
@@ -127,6 +128,16 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("error adding config keys to kms: %w", err)
|
||||
}
|
||||
// now that the kms is configured, we can get the audit wrapper and rotate
|
||||
// the eventer audit wrapper, so the emitted events can include encrypt and
|
||||
// hmac-sha256 data
|
||||
auditWrapper, err := c.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeAudit)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting audit wrapper from kms: %w", err)
|
||||
}
|
||||
if c.conf.Eventer.RotateAuditWrapper(ctx, auditWrapper); err != nil {
|
||||
return nil, fmt.Errorf("error rotating eventer audit wrapper: %w", err)
|
||||
}
|
||||
jobRepoFn := func() (*job.Repository, error) {
|
||||
return job.NewRepository(dbase, dbase, c.kms)
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ require (
|
||||
github.com/fatih/color v1.12.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/hashicorp/eventlogger v0.1.0
|
||||
github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-kms-wrapping v0.6.6
|
||||
|
||||
+2
-1
@@ -235,8 +235,9 @@ github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/eventlogger v0.1.0 h1:S6xc4gZVzewuDUP4R4Ngko419h/CGDuV/b4ADL3XLik=
|
||||
github.com/hashicorp/eventlogger v0.1.0/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0=
|
||||
github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144 h1:PCl0HtlVnIIloIozAKvjICu6K4IghKXAKNny3R3b2nI=
|
||||
github.com/hashicorp/eventlogger v0.1.1-0.20211104100552-e1e801e50144/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239 h1:Yh9tY0lige+y0trmjQeT9NRDo6+YvtNAzbmUNOsIUzI=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.6-0.20211027211326-5db60a48f239/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
|
||||
Reference in new issue
Block a user