Events: add redact/encrypt node for audit events to Eventer (#1490)

* feat (events): Add new options needed for AuditConfig

WithAuditWrapper and WithFilterOperations all for optional paramters
when creating AuditConfig types.

* refactor (event): Change EventerConfig.Sinks to a ptr

We need to modify the SinkConfig.AuditConfig during the NewEventer factory, so we have to use a ptr not a ref.

* feat (events): Add audit configuration types

* feat (events): add audit config to sink configuration.

* feat (events): add redact/encrypt node for audit events to Eventer

Integrate the encrypt.Filter node from the eventlogger into the
event.Eventer, so audit events can be redacted/encrypted based
on data classifications.

* test (events) Add test protobufs for events

Add test protobufs along with a test service for testing
the audit event encrypt node
This commit is contained in:
Jim authored and GitHub committed 2021-08-30 19:02:48 -04:00
1 parent 06ce257cb8
commit 5f46207e76
30 files changed
+1714 -715

No files matched your search

+5
View File
@@ -137,6 +137,11 @@ protobuild:
@protoc-go-inject-tag -input=./internal/credential/store/credential.pb.go
@protoc-go-inject-tag -input=./internal/credential/vault/store/vault.pb.go
# these protos, services and openapi artifacts are purely for testing purposes
@protoc-go-inject-tag -input=./internal/gen/testing/event/event.pb.go
@protoc --proto_path=internal/proto/local --proto_path=internal/proto/third_party --openapiv2_out=json_names_for_fields=false,logtostderr=true,disable_default_errors=true,include_package_in_tags=true,fqn_for_openapi_name=true,allow_merge,merge_file_name=testing:internal/gen/testing/event/. internal/proto/local/testing/event/v1/*.proto
@rm -R ${TMP_DIR}
protolint:
+2
View File
@@ -24,6 +24,7 @@ require (
github.com/hashicorp/dawdle v0.4.0
github.com/hashicorp/dbassert v0.0.0-20200930125617-6218396928df
github.com/hashicorp/eventlogger v0.1.0
github.com/hashicorp/eventlogger/filters/encrypt v0.1.3
github.com/hashicorp/go-bexpr v0.1.9
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-hclog v0.16.2
@@ -55,6 +56,7 @@ require (
github.com/mattn/go-colorable v0.1.8
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mitchellh/cli v1.1.2
github.com/mitchellh/copystructure v1.2.0
github.com/mitchellh/go-wordwrap v1.0.1
github.com/mitchellh/gox v1.0.1
github.com/mitchellh/mapstructure v1.4.1
+8 -570
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -33,7 +33,7 @@ func Test_GetOpts(t *testing.T) {
t.Run("WithEventerConfig", func(t *testing.T) {
assert := assert.New(t)
c := event.EventerConfig{
Sinks: []event.SinkConfig{
Sinks: []*event.SinkConfig{
// not a valid sink, but it doesn't need to be to test the
// option is properly supported.
{
+1 -1
View File
@@ -123,7 +123,7 @@ func TestServer_SetupEventing(t *testing.T) {
logger: testLogger,
lock: testLock,
opt: []Option{WithEventerConfig(&event.EventerConfig{
Sinks: []event.SinkConfig{
Sinks: []*event.SinkConfig{
{
Format: "invalid-format",
},
+2 -2
View File
@@ -487,10 +487,10 @@ func parseEventing(eventObj *ast.ObjectItem) (*event.EventerConfig, error) {
}
// Append to result
result.Sinks = append(result.Sinks, s)
result.Sinks = append(result.Sinks, &s)
}
if len(result.Sinks) == 0 {
result.Sinks = []event.SinkConfig{event.DefaultSink()}
result.Sinks = []*event.SinkConfig{event.DefaultSink()}
}
return &result, nil
}
+3 -3
View File
@@ -347,7 +347,7 @@ func TestController_EventingConfig(t *testing.T) {
wantEventerConfig: &event.EventerConfig{
AuditEnabled: true,
ObservationsEnabled: false,
Sinks: []event.SinkConfig{
Sinks: []*event.SinkConfig{
event.DefaultSink(),
},
},
@@ -362,7 +362,7 @@ func TestController_EventingConfig(t *testing.T) {
wantEventerConfig: &event.EventerConfig{
AuditEnabled: false,
ObservationsEnabled: true,
Sinks: []event.SinkConfig{
Sinks: []*event.SinkConfig{
event.DefaultSink(),
},
},
@@ -470,7 +470,7 @@ func TestController_EventingConfig(t *testing.T) {
wantEventerConfig: &event.EventerConfig{
AuditEnabled: false,
ObservationsEnabled: true,
Sinks: []event.SinkConfig{
Sinks: []*event.SinkConfig{
{
Type: "file",
Name: "configured-sink",
+299
View File
@@ -0,0 +1,299 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.15.8
// source: testing/event/v1/event.proto
package event
import (
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
structpb "google.golang.org/protobuf/types/known/structpb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type TestAuthenticateRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// The ID of the Auth Method in the system that should be used for authentication.
// @inject_tag: `class:"public"`
AuthMethodId string `protobuf:"bytes,1,opt,name=auth_method_id,proto3" json:"auth_method_id,omitempty" class:"public"`
// This can be "cookie" or "token". If not provided, "token" will be used. "cookie" activates a split-cookie method where the token is split partially between http-only and regular cookies in order
// to keep it safe from rogue JS in the browser.
// @inject_tag: `class:"public"`
TokenType string `protobuf:"bytes,2,opt,name=token_type,proto3" json:"token_type,omitempty" class:"public"`
// Attributes are passed to the Auth Method; the valid keys and values depend on the type of Auth Method as well as the command.
Attributes *structpb.Struct `protobuf:"bytes,4,opt,name=attributes,proto3" json:"attributes,omitempty"`
// The command to perform.
// @inject_tag: `class:"public"`
Command string `protobuf:"bytes,5,opt,name=command,proto3" json:"command,omitempty" class:"public"`
}
func (x *TestAuthenticateRequest) Reset() {
*x = TestAuthenticateRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_testing_event_v1_event_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TestAuthenticateRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TestAuthenticateRequest) ProtoMessage() {}
func (x *TestAuthenticateRequest) ProtoReflect() protoreflect.Message {
mi := &file_testing_event_v1_event_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TestAuthenticateRequest.ProtoReflect.Descriptor instead.
func (*TestAuthenticateRequest) Descriptor() ([]byte, []int) {
return file_testing_event_v1_event_proto_rawDescGZIP(), []int{0}
}
func (x *TestAuthenticateRequest) GetAuthMethodId() string {
if x != nil {
return x.AuthMethodId
}
return ""
}
func (x *TestAuthenticateRequest) GetTokenType() string {
if x != nil {
return x.TokenType
}
return ""
}
func (x *TestAuthenticateRequest) GetAttributes() *structpb.Struct {
if x != nil {
return x.Attributes
}
return nil
}
func (x *TestAuthenticateRequest) GetCommand() string {
if x != nil {
return x.Command
}
return ""
}
type TestAuthenticateResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Valid keys and values depend on the type of Auth Method as well as the command.
Attributes *structpb.Struct `protobuf:"bytes,4,opt,name=attributes,proto3" json:"attributes,omitempty"`
// The command that was performed.
// @inject_tag: `class:"public"`
Command string `protobuf:"bytes,5,opt,name=command,proto3" json:"command,omitempty" class:"public"`
}
func (x *TestAuthenticateResponse) Reset() {
*x = TestAuthenticateResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_testing_event_v1_event_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TestAuthenticateResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TestAuthenticateResponse) ProtoMessage() {}
func (x *TestAuthenticateResponse) ProtoReflect() protoreflect.Message {
mi := &file_testing_event_v1_event_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TestAuthenticateResponse.ProtoReflect.Descriptor instead.
func (*TestAuthenticateResponse) Descriptor() ([]byte, []int) {
return file_testing_event_v1_event_proto_rawDescGZIP(), []int{1}
}
func (x *TestAuthenticateResponse) GetAttributes() *structpb.Struct {
if x != nil {
return x.Attributes
}
return nil
}
func (x *TestAuthenticateResponse) GetCommand() string {
if x != nil {
return x.Command
}
return ""
}
var File_testing_event_v1_event_proto protoreflect.FileDescriptor
var file_testing_event_v1_event_proto_rawDesc = []byte{
0x0a, 0x1c, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2f,
0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10,
0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x31,
0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61,
0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e,
0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb4, 0x01, 0x0a,
0x17, 0x54, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68,
0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64,
0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65,
0x12, 0x37, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0a, 0x61,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x22, 0x8b, 0x01, 0x0a, 0x18, 0x54, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68,
0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x37, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0a, 0x61,
0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d,
0x6d, 0x61, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x52,
0x04, 0x69, 0x74, 0x65, 0x6d, 0x52, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70,
0x65, 0x32, 0x89, 0x02, 0x0a, 0x15, 0x54, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xef, 0x01, 0x0a, 0x10,
0x54, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65,
0x12, 0x29, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74,
0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69,
0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x74, 0x65,
0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x54,
0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x83, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x33,
0x22, 0x2e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2d, 0x6d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x73, 0x2f, 0x7b, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f,
0x69, 0x64, 0x7d, 0x3a, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65,
0x3a, 0x01, 0x2a, 0x92, 0x41, 0x47, 0x12, 0x45, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69,
0x63, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x61,
0x6e, 0x20, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x72, 0x65, 0x74, 0x72,
0x69, 0x65, 0x76, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69,
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x2e, 0x42, 0x40, 0x5a,
0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68,
0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x74, 0x65, 0x73, 0x74,
0x69, 0x6e, 0x67, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x3b, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_testing_event_v1_event_proto_rawDescOnce sync.Once
file_testing_event_v1_event_proto_rawDescData = file_testing_event_v1_event_proto_rawDesc
)
func file_testing_event_v1_event_proto_rawDescGZIP() []byte {
file_testing_event_v1_event_proto_rawDescOnce.Do(func() {
file_testing_event_v1_event_proto_rawDescData = protoimpl.X.CompressGZIP(file_testing_event_v1_event_proto_rawDescData)
})
return file_testing_event_v1_event_proto_rawDescData
}
var file_testing_event_v1_event_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_testing_event_v1_event_proto_goTypes = []interface{}{
(*TestAuthenticateRequest)(nil), // 0: testing.event.v1.TestAuthenticateRequest
(*TestAuthenticateResponse)(nil), // 1: testing.event.v1.TestAuthenticateResponse
(*structpb.Struct)(nil), // 2: google.protobuf.Struct
}
var file_testing_event_v1_event_proto_depIdxs = []int32{
2, // 0: testing.event.v1.TestAuthenticateRequest.attributes:type_name -> google.protobuf.Struct
2, // 1: testing.event.v1.TestAuthenticateResponse.attributes:type_name -> google.protobuf.Struct
0, // 2: testing.event.v1.TestAuthMethodService.TestAuthenticate:input_type -> testing.event.v1.TestAuthenticateRequest
1, // 3: testing.event.v1.TestAuthMethodService.TestAuthenticate:output_type -> testing.event.v1.TestAuthenticateResponse
3, // [3:4] is the sub-list for method output_type
2, // [2:3] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_testing_event_v1_event_proto_init() }
func file_testing_event_v1_event_proto_init() {
if File_testing_event_v1_event_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_testing_event_v1_event_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TestAuthenticateRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_testing_event_v1_event_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TestAuthenticateResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_testing_event_v1_event_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_testing_event_v1_event_proto_goTypes,
DependencyIndexes: file_testing_event_v1_event_proto_depIdxs,
MessageInfos: file_testing_event_v1_event_proto_msgTypes,
}.Build()
File_testing_event_v1_event_proto = out.File
file_testing_event_v1_event_proto_rawDesc = nil
file_testing_event_v1_event_proto_goTypes = nil
file_testing_event_v1_event_proto_depIdxs = nil
}
+201
View File
@@ -0,0 +1,201 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: testing/event/v1/event.proto
/*
Package event is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package event
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_TestAuthMethodService_TestAuthenticate_0(ctx context.Context, marshaler runtime.Marshaler, client TestAuthMethodServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TestAuthenticateRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["auth_method_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "auth_method_id")
}
protoReq.AuthMethodId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "auth_method_id", err)
}
msg, err := client.TestAuthenticate(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_TestAuthMethodService_TestAuthenticate_0(ctx context.Context, marshaler runtime.Marshaler, server TestAuthMethodServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq TestAuthenticateRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["auth_method_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "auth_method_id")
}
protoReq.AuthMethodId, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "auth_method_id", err)
}
msg, err := server.TestAuthenticate(ctx, &protoReq)
return msg, metadata, err
}
// RegisterTestAuthMethodServiceHandlerServer registers the http handlers for service TestAuthMethodService to "mux".
// UnaryRPC :call TestAuthMethodServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterTestAuthMethodServiceHandlerFromEndpoint instead.
func RegisterTestAuthMethodServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server TestAuthMethodServiceServer) error {
mux.Handle("POST", pattern_TestAuthMethodService_TestAuthenticate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/testing.event.v1.TestAuthMethodService/TestAuthenticate")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_TestAuthMethodService_TestAuthenticate_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_TestAuthMethodService_TestAuthenticate_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterTestAuthMethodServiceHandlerFromEndpoint is same as RegisterTestAuthMethodServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterTestAuthMethodServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterTestAuthMethodServiceHandler(ctx, mux, conn)
}
// RegisterTestAuthMethodServiceHandler registers the http handlers for service TestAuthMethodService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterTestAuthMethodServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterTestAuthMethodServiceHandlerClient(ctx, mux, NewTestAuthMethodServiceClient(conn))
}
// RegisterTestAuthMethodServiceHandlerClient registers the http handlers for service TestAuthMethodService
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "TestAuthMethodServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "TestAuthMethodServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "TestAuthMethodServiceClient" to call the correct interceptors.
func RegisterTestAuthMethodServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client TestAuthMethodServiceClient) error {
mux.Handle("POST", pattern_TestAuthMethodService_TestAuthenticate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/testing.event.v1.TestAuthMethodService/TestAuthenticate")
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_TestAuthMethodService_TestAuthenticate_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_TestAuthMethodService_TestAuthenticate_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_TestAuthMethodService_TestAuthenticate_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "auth-methods", "auth_method_id"}, "authenticate"))
)
var (
forward_TestAuthMethodService_TestAuthenticate_0 = runtime.ForwardResponseMessage
)
+105
View File
@@ -0,0 +1,105 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package event
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// TestAuthMethodServiceClient is the client API for TestAuthMethodService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type TestAuthMethodServiceClient interface {
// TestAuthenticate validates credentials provided and returns an Auth Token.
// Deprecated: use AuthenticateLogin instead.
TestAuthenticate(ctx context.Context, in *TestAuthenticateRequest, opts ...grpc.CallOption) (*TestAuthenticateResponse, error)
}
type testAuthMethodServiceClient struct {
cc grpc.ClientConnInterface
}
func NewTestAuthMethodServiceClient(cc grpc.ClientConnInterface) TestAuthMethodServiceClient {
return &testAuthMethodServiceClient{cc}
}
func (c *testAuthMethodServiceClient) TestAuthenticate(ctx context.Context, in *TestAuthenticateRequest, opts ...grpc.CallOption) (*TestAuthenticateResponse, error) {
out := new(TestAuthenticateResponse)
err := c.cc.Invoke(ctx, "/testing.event.v1.TestAuthMethodService/TestAuthenticate", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// TestAuthMethodServiceServer is the server API for TestAuthMethodService service.
// All implementations must embed UnimplementedTestAuthMethodServiceServer
// for forward compatibility
type TestAuthMethodServiceServer interface {
// TestAuthenticate validates credentials provided and returns an Auth Token.
// Deprecated: use AuthenticateLogin instead.
TestAuthenticate(context.Context, *TestAuthenticateRequest) (*TestAuthenticateResponse, error)
mustEmbedUnimplementedTestAuthMethodServiceServer()
}
// UnimplementedTestAuthMethodServiceServer must be embedded to have forward compatible implementations.
type UnimplementedTestAuthMethodServiceServer struct {
}
func (UnimplementedTestAuthMethodServiceServer) TestAuthenticate(context.Context, *TestAuthenticateRequest) (*TestAuthenticateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method TestAuthenticate not implemented")
}
func (UnimplementedTestAuthMethodServiceServer) mustEmbedUnimplementedTestAuthMethodServiceServer() {}
// UnsafeTestAuthMethodServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to TestAuthMethodServiceServer will
// result in compilation errors.
type UnsafeTestAuthMethodServiceServer interface {
mustEmbedUnimplementedTestAuthMethodServiceServer()
}
func RegisterTestAuthMethodServiceServer(s grpc.ServiceRegistrar, srv TestAuthMethodServiceServer) {
s.RegisterService(&TestAuthMethodService_ServiceDesc, srv)
}
func _TestAuthMethodService_TestAuthenticate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TestAuthenticateRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TestAuthMethodServiceServer).TestAuthenticate(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/testing.event.v1.TestAuthMethodService/TestAuthenticate",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TestAuthMethodServiceServer).TestAuthenticate(ctx, req.(*TestAuthenticateRequest))
}
return interceptor(ctx, in, info, handler)
}
// TestAuthMethodService_ServiceDesc is the grpc.ServiceDesc for TestAuthMethodService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var TestAuthMethodService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "testing.event.v1.TestAuthMethodService",
HandlerType: (*TestAuthMethodServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "TestAuthenticate",
Handler: _TestAuthMethodService_TestAuthenticate_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "testing/event/v1/event.proto",
}
+36
View File
@@ -0,0 +1,36 @@
package event
import "github.com/hashicorp/eventlogger/filters/encrypt"
// Tags implements the encrypt.Taggable interface which allows
// TestEventAuthenRequest Attributes to be classified for the encrypt filter.
func (req *TestAuthenticateRequest) Tags() ([]encrypt.PointerTag, error) {
var tags []encrypt.PointerTag
if req.Attributes != nil {
tags = append(tags, encrypt.PointerTag{
Pointer: "/Attributes/Fields/password",
Classification: encrypt.SecretClassification,
Filter: encrypt.RedactOperation,
})
}
return tags, nil
}
// Tags implements the encrypt.Taggable interface which allows
// TestEventAuthenResponse Attributes to be classified for the encrypt filter.
func (req *TestAuthenticateResponse) Tags() ([]encrypt.PointerTag, error) {
var tags []encrypt.PointerTag
if req.Attributes != nil {
tags = append(tags, encrypt.PointerTag{
Pointer: "/Attributes/Fields/token",
Classification: encrypt.SecretClassification,
Filter: encrypt.RedactOperation,
})
}
return tags, nil
}
@@ -0,0 +1,98 @@
{
"swagger": "2.0",
"info": {
"title": "testing/event/v1/event.proto",
"version": "version not set"
},
"tags": [
{
"name": "TestAuthMethodService"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/v1/auth-methods/{auth_method_id}:authenticate": {
"post": {
"summary": "Authenticate a user to an scope and retrieve an authentication token.",
"operationId": "TestAuthMethodService_TestAuthenticate",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/testing.event.v1.TestAuthenticateResponse"
}
}
},
"parameters": [
{
"name": "auth_method_id",
"description": "The ID of the Auth Method in the system that should be used for authentication.\n@inject_tag: `class:\"public\"`",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/testing.event.v1.TestAuthenticateRequest"
}
}
],
"tags": [
"testing.event.v1.TestAuthMethodService"
]
}
}
},
"definitions": {
"google.protobuf.NullValue": {
"type": "string",
"enum": [
"NULL_VALUE"
],
"default": "NULL_VALUE",
"description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
},
"testing.event.v1.TestAuthenticateRequest": {
"type": "object",
"properties": {
"auth_method_id": {
"type": "string",
"title": "The ID of the Auth Method in the system that should be used for authentication.\n@inject_tag: `class:\"public\"`"
},
"token_type": {
"type": "string",
"title": "This can be \"cookie\" or \"token\". If not provided, \"token\" will be used. \"cookie\" activates a split-cookie method where the token is split partially between http-only and regular cookies in order\nto keep it safe from rogue JS in the browser.\n@inject_tag: `class:\"public\"`"
},
"attributes": {
"type": "object",
"description": "Attributes are passed to the Auth Method; the valid keys and values depend on the type of Auth Method as well as the command."
},
"command": {
"type": "string",
"title": "The command to perform.\n@inject_tag: `class:\"public\"`"
}
}
},
"testing.event.v1.TestAuthenticateResponse": {
"type": "object",
"properties": {
"attributes": {
"type": "object",
"description": "Valid keys and values depend on the type of Auth Method as well as the command."
},
"command": {
"type": "string",
"title": "The command that was performed.\n@inject_tag: `class:\"public\"`"
}
}
}
}
}
@@ -0,0 +1,67 @@
package event
import (
"fmt"
wrapping "github.com/hashicorp/go-kms-wrapping"
)
// AuditConfig defines the configuration required for audit events sinks
type AuditConfig struct {
// FilterOverrides provide an optional a set of overrides for the
// FilterOperations to be applied to DataClassifications.
FilterOverrides AuditFilterOperations `hcl:"audit_filter_overrides"`
// wrapper to use for audit event crypto operations.
wrapper wrapping.Wrapper
}
// NewAuditConfig creates a new config starting with the DefaultAuditConfig()
// and applying options. Supported options are: WithWrapper and
// WithFilterOperations.
func NewAuditConfig(opt ...Option) (*AuditConfig, error) {
const op = "event.NewAuditConfig"
opts := getOpts(opt...)
c := DefaultAuditConfig()
if opts.withAuditWrapper != nil {
c.wrapper = opts.withAuditWrapper
}
if opts.withFilterOperations != nil {
c.FilterOverrides = opts.withFilterOperations
}
if err := c.Validate(); err != nil {
return nil, fmt.Errorf("%s: invalid configuration: %w", op, err)
}
return c, nil
}
// Validate the AuditConfig
func (ac *AuditConfig) Validate() error {
const op = "event.(AuditConfig).Validate"
// validate overrides first so all other checks can assert that they are
// valid.
if err := ac.FilterOverrides.Validate(); err != nil {
return fmt.Errorf("%s: %w", op, err)
}
if ac.wrapper == nil {
for _, filterOperation := range ac.FilterOverrides {
switch filterOperation {
case EncryptOperation, HmacSha256Operation:
return fmt.Errorf("%s: missing wrapper and %s filter operation requires a wrapper: %w", op, filterOperation, ErrInvalidParameter)
}
}
}
return nil
}
// DefaultAuditConfig specifies a default AuditConfig. The default config will
// redact both sensitive and secret classifications, so by default a wrapper is
// not required.
func DefaultAuditConfig() *AuditConfig {
return &AuditConfig{
FilterOverrides: DefaultAuditFilterOperations(),
}
}
@@ -0,0 +1,153 @@
package event
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDefaultAuditConfig(t *testing.T) {
assert := assert.New(t)
got := DefaultAuditConfig()
want := &AuditConfig{
FilterOverrides: DefaultAuditFilterOperations(),
}
assert.Equal(want, got)
}
func TestAuditConfig_Validate(t *testing.T) {
tests := []struct {
name string
ac *AuditConfig
wantIsError error
wantErrContains string
}{
{
name: "invalid-classification-override",
ac: &AuditConfig{
FilterOverrides: AuditFilterOperations{
"invalid-classification": RedactOperation,
},
},
wantIsError: ErrInvalidParameter,
wantErrContains: "invalid filter override classification (invalid-classification)",
},
{
name: "invalid-operation-override",
ac: &AuditConfig{
FilterOverrides: AuditFilterOperations{
SensitiveClassification: "invalid-operation",
},
},
wantIsError: ErrInvalidParameter,
wantErrContains: "invalid filter override operation (invalid-operation)",
},
{
name: "missing-wrapper-with-hmac-filter",
ac: &AuditConfig{
FilterOverrides: AuditFilterOperations{
SensitiveClassification: HmacSha256Operation,
},
},
wantIsError: ErrInvalidParameter,
wantErrContains: "hmac-sha256 filter operation requires a wrapper",
},
{
name: "missing-wrapper-with-encrypt-filter",
ac: &AuditConfig{
FilterOverrides: AuditFilterOperations{
SensitiveClassification: EncryptOperation,
},
},
wantIsError: ErrInvalidParameter,
wantErrContains: "encrypt filter operation requires a wrapper",
},
{
name: "valid-default",
ac: DefaultAuditConfig(),
},
{
name: "valid-with-required-wrapper-for-encrypt",
ac: &AuditConfig{
wrapper: testWrapper(t),
FilterOverrides: AuditFilterOperations{
SensitiveClassification: EncryptOperation,
},
},
},
{
name: "valid-with-required-wrapper-for-hmac",
ac: &AuditConfig{
wrapper: testWrapper(t),
FilterOverrides: AuditFilterOperations{
SensitiveClassification: HmacSha256Operation,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := tt.ac.Validate()
if tt.wantIsError != nil {
require.Error(err)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
})
}
}
func TestNewAuditConfig(t *testing.T) {
wrapper := testWrapper(t)
filterOps := AuditFilterOperations{
SensitiveClassification: EncryptOperation,
SecretClassification: EncryptOperation,
}
tests := []struct {
name string
opts []Option
want *AuditConfig
wantIsError error
wantErrContains string
}{
{
name: "missing-required-wrapper",
opts: []Option{WithFilterOperations(filterOps)},
wantIsError: ErrInvalidParameter,
wantErrContains: "missing wrapper",
},
{
name: "valid-default",
want: DefaultAuditConfig(),
},
{
name: "valid-with-all-opts",
opts: []Option{WithAuditWrapper(wrapper), WithFilterOperations(filterOps)},
want: &AuditConfig{
FilterOverrides: filterOps,
wrapper: wrapper,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := NewAuditConfig(tt.opts...)
if tt.wantIsError != nil {
require.Error(err)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tt.want, got)
})
}
}
+117 -47
View File
@@ -11,15 +11,12 @@ import (
"testing"
"time"
"github.com/golang/protobuf/ptypes/wrappers"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/host/static"
pbs "github.com/hashicorp/boundary/internal/gen/testing/event"
"github.com/hashicorp/boundary/internal/observability/event"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hosts"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"github.com/hashicorp/eventlogger/filters/encrypt"
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
"github.com/hashicorp/go-hclog"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
@@ -584,7 +581,7 @@ func Test_DefaultEventerConfig(t *testing.T) {
AuditEnabled: false,
ObservationsEnabled: true,
SysEventsEnabled: true,
Sinks: []event.SinkConfig{event.DefaultSink()},
Sinks: []*event.SinkConfig{event.DefaultSink()},
}, event.DefaultEventerConfig())
})
}
@@ -657,39 +654,46 @@ func Test_WriteAudit(t *testing.T) {
ctx, err = event.NewRequestInfoContext(ctx, info)
require.NoError(t, err)
testAuth := &event.Auth{}
testReq := &event.Request{
Operation: "POST",
Endpoint: "/v1/hosts",
Details: &pbs.CreateHostRequest{Item: &pb.Host{
HostCatalogId: "hc_1234567890",
Name: &wrappers.StringValue{Value: "name"},
Description: &wrappers.StringValue{Value: "desc"},
Type: "static",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"address": structpb.NewStringValue("123.456.789"),
}},
}},
}
testAuthorizedActions := []string{"no-op", "read", "update", "delete"}
testResp := &event.Response{
StatusCode: 200,
Details: &pbs.CreateHostResponse{
Uri: fmt.Sprintf("hosts/%s_", static.HostPrefix),
Item: &pb.Host{
HostCatalogId: "hc_1234567890",
Scope: &scopes.ScopeInfo{Id: "proj_1234567890", Type: scope.Project.String(), ParentScopeId: "org_1234567890"},
Name: &wrappers.StringValue{Value: "name"},
Description: &wrappers.StringValue{Value: "desc"},
Type: "static",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"address": structpb.NewStringValue("123.456.789"),
}},
AuthorizedActions: testAuthorizedActions,
testAuth := &event.Auth{
AuthTokenId: "test_auth_token_id",
UserEmail: "test_user_email",
UserName: "test_user_name",
UserInfo: &event.UserInfo{
UserId: "test_user_id",
AuthAccountId: "test_auth_account_id",
},
GrantsInfo: &event.GrantsInfo{
Grants: []event.GrantsPair{
{
Grant: "test_grant",
ScopeId: "test_grant_scope_id",
},
},
},
}
testReq := &event.Request{
Operation: "POST",
Endpoint: "/v1/hosts",
Details: &pbs.TestAuthenticateRequest{
AuthMethodId: "test_1234567890",
TokenType: "test-cookie",
Command: "test-command",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"password": structpb.NewStringValue("fido"),
}},
},
}
testResp := &event.Response{
StatusCode: 200,
Details: &pbs.TestAuthenticateResponse{
Command: "test-command",
Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{
"token": structpb.NewStringValue("test-token"),
}},
},
}
tests := []struct {
name string
auditOpts [][]event.Option
@@ -745,13 +749,40 @@ func Test_WriteAudit(t *testing.T) {
ctx: context.Background(),
auditOpts: [][]event.Option{
{
event.WithAuth(testAuth),
event.WithRequest(testReq),
event.WithAuth(
func() *event.Auth {
dup, err := copystructure.Copy(testAuth)
require.NoError(t, err)
return dup.(*event.Auth)
}(),
),
event.WithRequest(
func() *event.Request {
dup, err := copystructure.Copy(testReq)
require.NoError(t, err)
return dup.(*event.Request)
}(),
),
},
},
wantAudit: &testAudit{
Auth: testAuth,
Request: testReq,
Auth: func() *event.Auth {
dup, err := copystructure.Copy(testAuth)
require.NoError(t, err)
dup.(*event.Auth).UserEmail = encrypt.RedactedData
dup.(*event.Auth).UserName = encrypt.RedactedData
return dup.(*event.Auth)
}(),
Request: func() *event.Request {
dup, err := copystructure.Copy(testReq)
require.NoError(t, err)
dup.(*event.Request).Details.(*pbs.TestAuthenticateRequest).Attributes = &structpb.Struct{
Fields: map[string]*structpb.Value{
"password": structpb.NewStringValue(encrypt.RedactedData),
},
}
return dup.(*event.Request)
}(),
},
setup: func() error {
return event.InitSysEventer(testLogger, testLock, "use-syseventer", event.WithEventerConfig(&c.EventerConfig))
@@ -774,8 +805,23 @@ func Test_WriteAudit(t *testing.T) {
},
},
wantAudit: &testAudit{
Auth: testAuth,
Request: testReq,
Auth: func() *event.Auth {
dup, err := copystructure.Copy(testAuth)
require.NoError(t, err)
dup.(*event.Auth).UserEmail = encrypt.RedactedData
dup.(*event.Auth).UserName = encrypt.RedactedData
return dup.(*event.Auth)
}(),
Request: func() *event.Request {
dup, err := copystructure.Copy(testReq)
require.NoError(t, err)
dup.(*event.Request).Details.(*pbs.TestAuthenticateRequest).Attributes = &structpb.Struct{
Fields: map[string]*structpb.Value{
"password": structpb.NewStringValue(encrypt.RedactedData),
},
}
return dup.(*event.Request)
}(),
},
setup: func() error {
return event.InitSysEventer(testLogger, testLock, "use-syseventer", event.WithEventerConfig(&c.EventerConfig))
@@ -796,10 +842,34 @@ func Test_WriteAudit(t *testing.T) {
},
},
wantAudit: &testAudit{
Id: "411",
Auth: testAuth,
Request: testReq,
Response: testResp,
Id: "411",
Auth: func() *event.Auth {
dup, err := copystructure.Copy(testAuth)
require.NoError(t, err)
dup.(*event.Auth).UserEmail = encrypt.RedactedData
dup.(*event.Auth).UserName = encrypt.RedactedData
return dup.(*event.Auth)
}(),
Request: func() *event.Request {
dup, err := copystructure.Copy(testReq)
require.NoError(t, err)
dup.(*event.Request).Details.(*pbs.TestAuthenticateRequest).Attributes = &structpb.Struct{
Fields: map[string]*structpb.Value{
"password": structpb.NewStringValue(encrypt.RedactedData),
},
}
return dup.(*event.Request)
}(),
Response: func() *event.Response {
dup, err := copystructure.Copy(testResp)
require.NoError(t, err)
dup.(*event.Response).Details.(*pbs.TestAuthenticateResponse).Attributes = &structpb.Struct{
Fields: map[string]*structpb.Value{
"token": structpb.NewStringValue(encrypt.RedactedData),
},
}
return dup.(*event.Response)
}(),
},
auditSinkFileName: c.AllEvents.Name(),
},
@@ -0,0 +1,33 @@
package event
import "fmt"
// DataClassification defines a data classification (public, sensitive, secret, etc)
type DataClassification string
const (
UnknownClassification DataClassification = "unknown"
// PublicClassification declares a field as public data. No filter
// operations are ever performed on public data.
PublicClassification DataClassification = "public"
// SensitiveClassification declares a field as sensitive data. By default,
// sensitive data is encrypted unless there are AuditConfig.FilterOverrides
SensitiveClassification DataClassification = "sensitive"
// SecretClassification declares a field as secret data. By default,
// secret data is redacted unless there are AuditConfig.FilterOverrides
SecretClassification DataClassification = "secret"
)
// Validate the DataClassification
func (dc DataClassification) Validate() error {
const op = "event.(DataClassification).Validate"
switch dc {
case PublicClassification, SensitiveClassification, SecretClassification:
return nil
default:
return fmt.Errorf("%s: invalid data classification '%s': %w", op, dc, ErrInvalidParameter)
}
}
@@ -0,0 +1,39 @@
package event
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDataClassification_Validate(t *testing.T) {
tests := []struct {
name string
c DataClassification
wantIsErr error
wantErrContains string
}{
{name: "invalid........", c: "invalid", wantIsErr: ErrInvalidParameter, wantErrContains: "invalid data classification 'invalid'"},
{name: "invalid-unknown", c: UnknownClassification, wantIsErr: ErrInvalidParameter, wantErrContains: "invalid data classification 'unknown'"},
{name: "valid-sensitive", c: SensitiveClassification},
{name: "valid-public...", c: PublicClassification},
{name: "valid-secret...", c: SecretClassification},
{name: "valid-unknown..", c: SecretClassification},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := tt.c.Validate()
if tt.wantIsErr != nil {
require.Error(err)
assert.ErrorIs(err, tt.wantIsErr)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
})
}
}
+16 -17
View File
@@ -12,16 +12,16 @@ type (
// RequestInfo defines the fields captured about a Boundary request.
type RequestInfo struct {
EventId string `json:"-"`
Id string `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Path string `json:"path,omitempty"`
PublicId string `json:"public_id,omitempty"`
Id string `json:"id,omitempty" class:"public"`
Method string `json:"method,omitempty" class:"public"`
Path string `json:"path,omitempty" class:"public"`
PublicId string `json:"public_id,omitempty" class:"public"`
}
// UserInfo defines the fields captured about a user for a Boundary request.
type UserInfo struct {
UserId string `json:"id,omitempty"`
AuthAccountId string `json:"auth_account_id,omitempty"`
UserId string `json:"id,omitempty" class:"public"`
AuthAccountId string `json:"auth_account_id,omitempty" class:"public"`
}
type GrantsInfo struct {
@@ -29,23 +29,22 @@ type GrantsInfo struct {
}
type GrantsPair struct {
Grant string `json:"grant,omitempty"`
ScopeId string `json:"scope_id,omitempty"`
Grant string `json:"grant,omitempty" class:"public"`
ScopeId string `json:"scope_id,omitempty" class:"public"`
}
type Auth struct {
// AccessorId is a std audit field == auth_token public_id
AccessorId string `json:"accessor_id"`
UserInfo *UserInfo `json:"user_info,omitempty"` // boundary field
GrantsInfo *GrantsInfo `json:"grants_info,omitempty"`
UserEmail string `json:"email,omitempty"`
UserName string `json:"name,omitempty"`
AuthTokenId string `json:"auth_token_id" class:"public"`
UserInfo *UserInfo `json:"user_info,omitempty"` // boundary field
GrantsInfo *GrantsInfo `json:"grants_info,omitempty"`
UserEmail string `json:"email,omitempty" class:"sensitive"`
UserName string `json:"name,omitempty" class:"sensitive"`
}
type Request struct {
Operation string `json:"operation"` // std audit field
Endpoint string `json:"endpoint"` // std audit field
Details proto.Message `json:"details"` // boundary field
Operation string `json:"operation,omitempty" class:"public"` // std audit field
Endpoint string `json:"endpoint,omitempty" class:"public"` // std audit field
Details proto.Message `json:"details,omitempty"` // boundary field
}
type Response struct {
+56 -22
View File
@@ -15,6 +15,7 @@ import (
"time"
"github.com/hashicorp/eventlogger"
"github.com/hashicorp/eventlogger/filters/encrypt"
"github.com/hashicorp/eventlogger/filters/gated"
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
"github.com/hashicorp/eventlogger/sinks/writer"
@@ -68,11 +69,12 @@ type Eventer struct {
}
type pipeline struct {
eventType Type
fmtId eventlogger.NodeID
sinkId eventlogger.NodeID
gateId eventlogger.NodeID
sinkConfig SinkConfig
eventType Type
fmtId eventlogger.NodeID
sinkId eventlogger.NodeID
gateId eventlogger.NodeID
encryptFilterId eventlogger.NodeID
sinkConfig *SinkConfig
}
var (
@@ -136,7 +138,7 @@ func SysEventer() *Eventer {
}
// NewEventer creates a new Eventer using the config. Supports options:
// WithNow, WithSerializationLock, WithBroker
// WithNow, WithSerializationLock, WithBroker, WithAuditWrapper
func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName string, c EventerConfig, opt ...Option) (*Eventer, error) {
const op = "event.NewEventer"
if log == nil {
@@ -192,7 +194,7 @@ 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)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
@@ -259,11 +261,38 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
}
}
if addToAudit {
var fop AuditFilterOperations
if s.AuditConfig != nil {
fop = s.AuditConfig.FilterOverrides
}
s.AuditConfig, err = NewAuditConfig(WithAuditWrapper(opts.withAuditWrapper), WithFilterOperations(fop))
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
encryptFilter := &encrypt.Filter{
Wrapper: opts.withAuditWrapper,
}
if len(s.AuditConfig.FilterOverrides) > 0 {
overrides := encrypt.DefaultFilterOperations()
for k, v := range s.AuditConfig.FilterOverrides {
overrides[encrypt.DataClassification(k)] = encrypt.FilterOperation(v)
}
encryptFilter.FilterOperationOverrides = overrides
}
id, err := NewId("encrypt-audit")
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
encryptFilterId := eventlogger.NodeID(id)
if err := b.RegisterNode(encryptFilterId, encryptFilter); err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
auditPipelines = append(auditPipelines, pipeline{
eventType: AuditType,
fmtId: fmtId,
sinkId: sinkId,
sinkConfig: s,
eventType: AuditType,
fmtId: fmtId,
sinkId: sinkId,
encryptFilterId: encryptFilterId,
sinkConfig: s,
})
}
if addToObservation {
@@ -322,7 +351,8 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
err = e.broker.RegisterPipeline(eventlogger.Pipeline{
EventType: eventlogger.EventType(p.eventType),
PipelineID: eventlogger.PipelineID(pipeId),
NodeIDs: []eventlogger.NodeID{p.gateId, p.fmtId, p.sinkId},
// order of nodes is important! gate (aggregate), then filter/format, then encrypt, then write to sink
NodeIDs: []eventlogger.NodeID{p.gateId, p.encryptFilterId, p.fmtId, p.sinkId},
})
if err != nil {
return nil, fmt.Errorf("%s: failed to register audit pipeline: %w", op, err)
@@ -352,7 +382,8 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
err = e.broker.RegisterPipeline(eventlogger.Pipeline{
EventType: eventlogger.EventType(p.eventType),
PipelineID: eventlogger.PipelineID(pipeId),
NodeIDs: []eventlogger.NodeID{p.gateId, p.fmtId, p.sinkId},
// order of nodes is important! gate (aggregate), then filter/format, then write to sink
NodeIDs: []eventlogger.NodeID{p.gateId, p.fmtId, p.sinkId},
})
if err != nil {
return nil, fmt.Errorf("%s: failed to register observation pipeline: %w", op, err)
@@ -368,7 +399,8 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
err = e.broker.RegisterPipeline(eventlogger.Pipeline{
EventType: eventlogger.EventType(p.eventType),
PipelineID: eventlogger.PipelineID(pipeId),
NodeIDs: []eventlogger.NodeID{p.fmtId, p.sinkId},
// order of nodes is important! filter/format, then write to sink
NodeIDs: []eventlogger.NodeID{p.fmtId, p.sinkId},
})
if err != nil {
return nil, fmt.Errorf("%s: failed to register err pipeline: %w", op, err)
@@ -384,7 +416,8 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
err = e.broker.RegisterPipeline(eventlogger.Pipeline{
EventType: eventlogger.EventType(p.eventType),
PipelineID: eventlogger.PipelineID(pipeId),
NodeIDs: []eventlogger.NodeID{p.fmtId, p.sinkId},
// order of nodes is important! filter/format, then write to sink
NodeIDs: []eventlogger.NodeID{p.fmtId, p.sinkId},
})
if err != nil {
return nil, fmt.Errorf("%s: failed to register sys pipeline: %w", op, err)
@@ -469,16 +502,17 @@ func DefaultEventerConfig() *EventerConfig {
AuditEnabled: false,
ObservationsEnabled: true,
SysEventsEnabled: true,
Sinks: []SinkConfig{DefaultSink()},
Sinks: []*SinkConfig{DefaultSink()},
}
}
func DefaultSink() SinkConfig {
return SinkConfig{
Name: "default",
EventTypes: []Type{EveryType},
Format: JSONSinkFormat,
Type: StderrSink,
func DefaultSink() *SinkConfig {
return &SinkConfig{
Name: "default",
EventTypes: []Type{EveryType},
Format: JSONSinkFormat,
Type: StderrSink,
AuditConfig: DefaultAuditConfig(),
}
}
@@ -6,10 +6,10 @@ import (
// EventerConfig supplies all the configuration needed to create/config an Eventer.
type EventerConfig struct {
AuditEnabled bool `hcl:"audit_enabled"` // AuditEnabled specifies if audit events should be emitted.
ObservationsEnabled bool `hcl:"observations_enabled"` // ObservationsEnabled specifies if observation events should be emitted.
SysEventsEnabled bool `hcl:"sysevents_enabled"` // SysEventsEnabled specifies if sysevents should be emitted.
Sinks []SinkConfig `hcl:"-"` // Sinks are all the configured sinks
AuditEnabled bool `hcl:"audit_enabled"` // AuditEnabled specifies if audit events should be emitted.
ObservationsEnabled bool `hcl:"observations_enabled"` // ObservationsEnabled specifies if observation events should be emitted.
SysEventsEnabled bool `hcl:"sysevents_enabled"` // SysEventsEnabled specifies if sysevents should be emitted.
Sinks []*SinkConfig `hcl:"-"` // Sinks are all the configured sinks
}
// Validate will Validate the config. A config isn't required to have any
@@ -17,7 +17,7 @@ func TestEventerConfig_Validate(t *testing.T) {
{
name: "invalid-sink",
c: EventerConfig{
Sinks: []SinkConfig{
Sinks: []*SinkConfig{
{
Type: "invalid",
},
+132 -24
View File
@@ -82,7 +82,7 @@ func Test_InitSysEventer(t *testing.T) {
},
{
name: "bad-config",
opt: []Option{WithEventerConfig(&EventerConfig{Sinks: []SinkConfig{{Format: "bad-format"}}})},
opt: []Option{WithEventerConfig(&EventerConfig{Sinks: []*SinkConfig{{Format: "bad-format"}}})},
log: testLogger,
lock: testLock,
serverName: "success-with-config",
@@ -108,12 +108,13 @@ func Test_InitSysEventer(t *testing.T) {
want: &Eventer{
logger: testLogger,
conf: EventerConfig{
Sinks: []SinkConfig{
Sinks: []*SinkConfig{
{
Name: "default",
EventTypes: []Type{EveryType},
Format: JSONSinkFormat,
Type: StderrSink,
Name: "default",
EventTypes: []Type{EveryType},
Format: JSONSinkFormat,
Type: StderrSink,
AuditConfig: DefaultAuditConfig(),
},
},
},
@@ -362,19 +363,110 @@ func Test_NewEventer(t *testing.T) {
Name: "test",
})
twrapper := testWrapper(t)
tests := []struct {
name string
config EventerConfig
opts []Option
logger hclog.Logger
lock *sync.Mutex
serverName string
want *Eventer
wantRegistered []string
wantPipelines []string
wantThresholds map[eventlogger.EventType]int
wantErrIs error
name string
config EventerConfig
opts []Option
logger hclog.Logger
lock *sync.Mutex
serverName string
want *Eventer
wantRegistered []string
wantPipelines []string
wantThresholds map[eventlogger.EventType]int
wantErrIs error
wantErrContains string
}{
{
name: "missing-require-wrapper",
config: func() EventerConfig {
cfg := EventerConfig{
AuditEnabled: true,
Sinks: []*SinkConfig{
{
Name: "test",
EventTypes: []Type{AuditType},
Type: StderrSink,
Format: JSONHclogSinkFormat,
AuditConfig: &AuditConfig{
FilterOverrides: AuditFilterOperations{
SensitiveClassification: EncryptOperation,
},
},
},
},
}
return cfg
}(),
lock: testLock,
logger: testLogger,
serverName: "missing-required-wrapper",
wantErrIs: ErrInvalidParameter,
wantErrContains: "missing wrapper and encrypt filter operation requires a wrapper",
},
{
name: "valid-audit-config-with-wrapper",
config: func() EventerConfig {
cfg := EventerConfig{
AuditEnabled: true,
Sinks: []*SinkConfig{
{
Name: "test",
EventTypes: []Type{AuditType},
Type: StderrSink,
Format: JSONSinkFormat,
AuditConfig: &AuditConfig{
FilterOverrides: AuditFilterOperations{
SensitiveClassification: EncryptOperation,
},
},
},
},
}
return cfg
}(),
opts: []Option{WithAuditWrapper(twrapper)},
lock: testLock,
logger: testLogger,
serverName: "valid-audit-config-with-wrapper",
want: &Eventer{
logger: testLogger,
conf: EventerConfig{
AuditEnabled: true,
Sinks: []*SinkConfig{
{
Name: "test",
EventTypes: []Type{AuditType},
Format: JSONSinkFormat,
Type: StderrSink,
AuditConfig: &AuditConfig{
wrapper: twrapper,
FilterOverrides: AuditFilterOperations{
SensitiveClassification: EncryptOperation,
},
},
},
},
},
},
wantRegistered: []string{
"cloudevents", // fmt for everything
"stderr", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
},
wantPipelines: []string{
"audit", // stderr
},
wantThresholds: map[eventlogger.EventType]int{
"error": 0,
"system": 0,
"observation": 0,
"audit": 1,
},
},
{
name: "missing-logger",
config: testSetup.EventerConfig,
@@ -401,7 +493,7 @@ func Test_NewEventer(t *testing.T) {
config: func() EventerConfig {
dupFileConfig := TestEventerConfig(t, "dup-sink-filename")
dupFileConfig.EventerConfig.Sinks = append(dupFileConfig.EventerConfig.Sinks,
SinkConfig{
&SinkConfig{
Name: "err-file-sink",
Type: FileSink,
EventTypes: []Type{ErrorType},
@@ -428,12 +520,13 @@ func Test_NewEventer(t *testing.T) {
want: &Eventer{
logger: testLogger,
conf: EventerConfig{
Sinks: []SinkConfig{
Sinks: []*SinkConfig{
{
Name: "default",
EventTypes: []Type{EveryType},
Format: JSONSinkFormat,
Type: StderrSink,
Name: "default",
EventTypes: []Type{EveryType},
Format: JSONSinkFormat,
Type: StderrSink,
AuditConfig: DefaultAuditConfig(),
},
},
},
@@ -443,6 +536,7 @@ func Test_NewEventer(t *testing.T) {
"stderr", // stderr
"gated-observation", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
},
wantPipelines: []string{
"audit", // stderr
@@ -472,10 +566,12 @@ func Test_NewEventer(t *testing.T) {
"stderr", // stderr
"gated-observation", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
"cloudevents", // every-type-file-sync
"tmp-all-events", // every-type-file-sync
"gated-observation", // every-type-file-sync
"gated-audit", // every-type-file-sync
"encrypt-audit", // every-type-file-sync
"cloudevents", // error-file-sink
"tmp-errors", // error-file-sink
},
@@ -512,10 +608,12 @@ func Test_NewEventer(t *testing.T) {
"stderr", // stderr
"gated-observation", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
"cloudevents", // every-type-file-sync
"tmp-all-events", // every-type-file-sync
"gated-observation", // every-type-file-sync
"gated-audit", // every-type-file-sync
"encrypt-audit", // every-type-file-sync
"cloudevents", // error-file-sink
"tmp-errors", // error-file-sink
"cloudevents", // observation-file-sink
@@ -523,6 +621,7 @@ func Test_NewEventer(t *testing.T) {
"tmp-observation", // observations-file-sink
"cloudevents", // audit-file-sink
"gated-audit", // audit-file-sink
"encrypt-audit", // audit-file-sink
"tmp-audit", // audit-file-sink
"cloudevents", // sys-file-sink
"tmp-sysevents", // sys-file-sink
@@ -563,10 +662,12 @@ func Test_NewEventer(t *testing.T) {
"stderr", // stderr
"gated-observation", // stderr
"gated-audit", // stderr
"encrypt-audit", // stderr
"hclog-text", // every-type-file-sync
"tmp-all-events", // every-type-file-sync
"gated-observation", // every-type-file-sync
"gated-audit", // every-type-file-sync
"encrypt-audit", // every-type-file-sync
"hclog-text", // error-file-sink
"tmp-errors", // error-file-sink
},
@@ -594,11 +695,18 @@ func Test_NewEventer(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
testBroker := &testMockBroker{}
got, err := NewEventer(tt.logger, tt.lock, tt.serverName, tt.config, TestWithBroker(t, testBroker))
opts := []Option{TestWithBroker(t, testBroker)}
if tt.opts != nil {
opts = append(opts, tt.opts...)
}
got, err := NewEventer(tt.logger, tt.lock, tt.serverName, tt.config, opts...)
if tt.wantErrIs != nil {
require.Error(err)
require.Nil(got)
assert.ErrorIs(err, tt.wantErrIs)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
@@ -0,0 +1,55 @@
package event
import (
"fmt"
)
// FilterOperation defines a filter operation (none, redact, encrypt, etc)
type FilterOperation string
const (
NoOperation FilterOperation = "" // NoOperation specifies no operation.
UnknownOperation FilterOperation = "unknown" // UnknownOperation specifies an unknown operation.
RedactOperation FilterOperation = "redact" // RedactOperation specifies an redaction operation
EncryptOperation FilterOperation = "encrypt" // EncryptOperation specifies an encryption operation.
HmacSha256Operation FilterOperation = "hmac-sha256" // HmacSha256Operation specifies an hmac-sha256 operation
)
// Validate the FilterOperation
func (fop FilterOperation) Validate() error {
const op = "event.(DataClassification).Validate"
switch fop {
case RedactOperation, EncryptOperation, HmacSha256Operation, NoOperation:
return nil
default:
return fmt.Errorf("%s: invalid filter operation '%s': %w", op, fop, ErrInvalidParameter)
}
}
// AuditFilterOperation defines a map between DataClassifications and
// FilterOperations for audit filtering
type AuditFilterOperations map[DataClassification]FilterOperation
// Validate the AuditFilterOperation
func (af AuditFilterOperations) Validate() error {
const op = "event.(AuditFilterOperations).Validate"
for k, v := range af {
if err := k.Validate(); err != nil {
return fmt.Errorf("%s: invalid filter override classification (%s): %w", op, k, err)
}
if err := v.Validate(); err != nil {
return fmt.Errorf("%s: invalid filter override operation (%s): %w", op, v, err)
}
}
return nil
}
// DefaultAuditFilterOperations will return a map of the default
// AuditConfig.AuditFilters
func DefaultAuditFilterOperations() AuditFilterOperations {
const op = "event.DefaultAuditFilterOperations"
return AuditFilterOperations{
SensitiveClassification: RedactOperation,
SecretClassification: RedactOperation,
}
}
@@ -0,0 +1,49 @@
package event
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFilterOperation_Validate(t *testing.T) {
tests := []struct {
name string
fop FilterOperation
wantIsErr error
wantErrContains string
}{
{name: "invalid........", fop: "invalid", wantIsErr: ErrInvalidParameter, wantErrContains: "invalid filter operation 'invalid'"},
{name: "invalid-unknown", fop: UnknownOperation, wantIsErr: ErrInvalidParameter, wantErrContains: "invalid filter operation 'unknown'"},
{name: "valid-sensitive", fop: RedactOperation},
{name: "valid-public...", fop: EncryptOperation},
{name: "valid-secret...", fop: HmacSha256Operation},
{name: "valid-unknown..", fop: NoOperation},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := tt.fop.Validate()
if tt.wantIsErr != nil {
require.Error(err)
assert.ErrorIs(err, tt.wantIsErr)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
})
}
}
func TestDefaultAuditFilterOperation(t *testing.T) {
assert := assert.New(t)
got := DefaultAuditFilterOperations()
want := AuditFilterOperations{
SecretClassification: RedactOperation,
SensitiveClassification: RedactOperation,
}
assert.Equal(want, got)
}
+33 -15
View File
@@ -3,6 +3,8 @@ package event
import (
"net/url"
"time"
wrapping "github.com/hashicorp/go-kms-wrapping"
)
const msgField = "msg"
@@ -23,21 +25,23 @@ type Option func(*options)
// options = how options are represented
type options struct {
withId string
withDetails map[string]interface{}
withHeader map[string]interface{}
withFlush bool
withInfo map[string]interface{}
withRequestInfo *RequestInfo
withNow time.Time
withRequest *Request
withResponse *Response
withAuth *Auth
withEventer *Eventer
withEventerConfig *EventerConfig
withAllow []string
withDeny []string
withSchema *url.URL
withId string
withDetails map[string]interface{}
withHeader map[string]interface{}
withFlush bool
withInfo map[string]interface{}
withRequestInfo *RequestInfo
withNow time.Time
withRequest *Request
withResponse *Response
withAuth *Auth
withEventer *Eventer
withEventerConfig *EventerConfig
withAllow []string
withDeny []string
withSchema *url.URL
withAuditWrapper wrapping.Wrapper
withFilterOperations AuditFilterOperations
withBroker broker // test only option
withAuditSink bool // test only option
@@ -178,3 +182,17 @@ func WithDeny(f ...string) Option {
o.withDeny = f
}
}
// WithAuditWrapper is an optional wrapper for audit events
func WithAuditWrapper(w wrapping.Wrapper) Option {
return func(o *options) {
o.withAuditWrapper = w
}
}
// WithFilterOperations is an optional set of filter operations
func WithFilterOperations(fop AuditFilterOperations) Option {
return func(o *options) {
o.withFilterOperations = fop
}
}
@@ -1,10 +1,14 @@
package event
import (
"encoding/base64"
"math/rand"
"net/url"
"testing"
"time"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/go-kms-wrapping/wrappers/aead"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -150,4 +154,45 @@ func Test_GetOpts(t *testing.T) {
testOpts.withSchema = schema
assert.Equal(opts, testOpts)
})
t.Run("WithAuditWrapper", func(t *testing.T) {
assert := assert.New(t)
w := testWrapper(t)
opts := getOpts(WithAuditWrapper(w))
testOpts := getDefaultOptions()
testOpts.withAuditWrapper = w
assert.Equal(opts, testOpts)
})
t.Run("WithFilterOperations", func(t *testing.T) {
assert := assert.New(t)
overrides := DefaultAuditFilterOperations()
opts := getOpts(WithFilterOperations(overrides))
testOpts := getDefaultOptions()
testOpts.withFilterOperations = overrides
assert.Equal(opts, testOpts)
})
}
// testWrapper initializes an AEAD wrapping.Wrapper for testing. Note: this
// code was cut/pasted from internal/db to eliminate a circular dependency with
// the internal/errors pkg
func testWrapper(t *testing.T) wrapping.Wrapper {
rootKey := make([]byte, 32)
n, err := rand.Read(rootKey)
if err != nil {
t.Fatal(err)
}
if n != 32 {
t.Fatal(n)
}
root := aead.NewWrapper(nil)
_, err = root.SetConfig(map[string]string{
"key_id": base64.StdEncoding.EncodeToString(rootKey),
})
if err != nil {
t.Fatal(err)
}
if err := root.SetAESGCMKeyBytes(rootKey); err != nil {
t.Fatal(err)
}
return root
}
@@ -17,6 +17,7 @@ type SinkConfig struct {
Type SinkType `hcl:"type"` // Type defines the type of sink (StderrSink or FileSink).
StderrConfig *StderrSinkTypeConfig `hcl:"stderr"` // StderrConfig defines parameters for a stderr output.
FileConfig *FileSinkTypeConfig `hcl:"file"` // FileConfig defines parameters for a file output.
AuditConfig *AuditConfig `hcl:"audit_config"` // AuditConfig defines optional parameters for audit events (if EventTypes contains audit)
}
func (sc *SinkConfig) Validate() error {
@@ -62,11 +63,22 @@ func (sc *SinkConfig) Validate() error {
if len(sc.EventTypes) == 0 {
return fmt.Errorf("%s: missing event types: %w", op, ErrInvalidParameter)
}
for _, et := range sc.EventTypes {
if err := et.Validate(); err != nil {
return fmt.Errorf("%s: %w", op, err)
}
// well, if there's an event type of audit, we need to check the audit
// config, if it's optionally provided. We are intentionally only
// checking the FilterOverrides, because there's no way to specify the
// wrapper in a config.
if (et == AuditType || et == EveryType) && sc.AuditConfig != nil && sc.AuditConfig.FilterOverrides != nil {
if err := sc.AuditConfig.FilterOverrides.Validate(); err != nil {
return fmt.Errorf("%s: invalid audit config: %w", op, err)
}
}
}
return nil
}
@@ -15,6 +15,44 @@ func TestSinkConfig_Validate(t *testing.T) {
wantErrIs error
wantErrContains string
}{
{
name: "invalid-everytype-audit-config",
sc: SinkConfig{
Name: "sink-name",
EventTypes: []Type{EveryType},
Type: FileSink,
FileConfig: &FileSinkTypeConfig{
FileName: "tmp.file",
},
Format: JSONSinkFormat,
AuditConfig: &AuditConfig{
FilterOverrides: AuditFilterOperations{
SensitiveClassification: "invalid",
},
},
},
wantErrIs: ErrInvalidParameter,
wantErrContains: "invalid audit config",
},
{
name: "invalid-auditaudit-config",
sc: SinkConfig{
Name: "sink-name",
EventTypes: []Type{AuditType},
Type: FileSink,
FileConfig: &FileSinkTypeConfig{
FileName: "tmp.file",
},
Format: JSONSinkFormat,
AuditConfig: &AuditConfig{
FilterOverrides: AuditFilterOperations{
"invalid": EncryptOperation,
},
},
},
wantErrIs: ErrInvalidParameter,
wantErrContains: "invalid audit config",
},
{
name: "missing-name",
sc: SinkConfig{
@@ -195,3 +233,48 @@ func TestSinkConfig_Validate(t *testing.T) {
})
}
}
func TestSinkFilter_Validate(t *testing.T) {
tests := []struct {
name string
sf *SinkFilter
wantErrIs error
wantErrContains string
}{
{
name: "invalid-type",
sf: &SinkFilter{
Type: "invalid",
Filter: `"/Data" ==1`,
},
wantErrContains: "invalid filter type",
},
{
name: "invalid-filter-bad-path",
sf: &SinkFilter{
Type: AllowFilter,
Filter: `"data" == 1`,
},
wantErrContains: "invalid filter '\"data\" == 1'",
},
{
name: "valid",
sf: &SinkFilter{
Type: DenyFilter,
Filter: `"/Data" ==1`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
err := tt.sf.Validate()
if tt.wantErrContains != "" {
require.Error(err)
assert.Contains(err.Error(), tt.wantErrContains)
return
}
assert.NoError(err)
})
}
}
+11 -8
View File
@@ -76,7 +76,7 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
EventerConfig: EventerConfig{
ObservationsEnabled: true,
AuditEnabled: true,
Sinks: []SinkConfig{
Sinks: []*SinkConfig{
{
Name: "every-type-file-sink",
Type: FileSink,
@@ -86,12 +86,14 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
Path: "./",
FileName: tmpAllFile.Name(),
},
AuditConfig: DefaultAuditConfig(),
},
{
Name: "stderr",
Type: StderrSink,
EventTypes: []Type{EveryType},
Format: opts.withSinkFormat,
Name: "stderr",
Type: StderrSink,
EventTypes: []Type{EveryType},
Format: opts.withSinkFormat,
AuditConfig: DefaultAuditConfig(),
},
{
Name: "err-file-sink",
@@ -114,7 +116,7 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
t.Cleanup(func() {
os.Remove(tmpFile.Name())
})
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, SinkConfig{
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, &SinkConfig{
Name: "audit-file-sink",
Type: FileSink,
EventTypes: []Type{AuditType},
@@ -123,6 +125,7 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
Path: "./",
FileName: tmpFile.Name(),
},
AuditConfig: DefaultAuditConfig(),
})
c.AuditEvents = tmpFile
}
@@ -132,7 +135,7 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
t.Cleanup(func() {
os.Remove(tmpFile.Name())
})
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, SinkConfig{
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, &SinkConfig{
Name: "err-observation-sink",
Type: FileSink,
EventTypes: []Type{ObservationType},
@@ -150,7 +153,7 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
t.Cleanup(func() {
os.Remove(tmpFile.Name())
})
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, SinkConfig{
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, &SinkConfig{
Name: "err-sysevents-sink",
Type: FileSink,
EventTypes: []Type{SystemType},
@@ -0,0 +1,47 @@
syntax = "proto3";
package testing.event.v1;
option go_package = "github.com/hashicorp/boundary/internal/gen/testing/event;event";
import "google/protobuf/struct.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "google/api/annotations.proto";
service TestAuthMethodService {
// TestAuthenticate validates credentials provided and returns an Auth Token.
// Deprecated: use AuthenticateLogin instead.
rpc TestAuthenticate(TestAuthenticateRequest) returns (TestAuthenticateResponse) {
option (google.api.http) = {
post: "/v1/auth-methods/{auth_method_id}:authenticate"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Authenticate a user to an scope and retrieve an authentication token."
};
}
}
message TestAuthenticateRequest {
// The ID of the Auth Method in the system that should be used for authentication.
// @inject_tag: `class:"public"`
string auth_method_id = 1 [json_name = "auth_method_id"];
// This can be "cookie" or "token". If not provided, "token" will be used. "cookie" activates a split-cookie method where the token is split partially between http-only and regular cookies in order
// to keep it safe from rogue JS in the browser.
// @inject_tag: `class:"public"`
string token_type = 2 [json_name = "token_type"];
// Attributes are passed to the Auth Method; the valid keys and values depend on the type of Auth Method as well as the command.
google.protobuf.Struct attributes = 4 [json_name = "attributes"];
// The command to perform.
// @inject_tag: `class:"public"`
string command = 5 [json_name = "command"];
}
message TestAuthenticateResponse {
reserved 1, 2; // Old item and token_type
reserved "item", "token_type";
// Valid keys and values depend on the type of Auth Method as well as the command.
google.protobuf.Struct attributes = 4 [json_name = "attributes"];
// The command that was performed.
// @inject_tag: `class:"public"`
string command = 5 [json_name = "command"];
}