VAULT-24452: audit refactor (#26460)

* Refactor audit code into audit package
* remove builtin/audit
* removed unrequired files
This commit is contained in:
Peter Wilson
2024-04-18 08:25:04 +01:00
committed by GitHub
parent 961bf20bdb
commit 8bee54c89d
60 changed files with 2638 additions and 3214 deletions

View File

@@ -24,10 +24,7 @@ import (
"github.com/jefferai/jsonx"
)
var (
_ Formatter = (*EntryFormatter)(nil)
_ eventlogger.Node = (*EntryFormatter)(nil)
)
var _ eventlogger.Node = (*entryFormatter)(nil)
// timeProvider offers a way to supply a pre-configured time.
type timeProvider interface {
@@ -35,11 +32,14 @@ type timeProvider interface {
formattedTime() string
}
// FormatterConfig is used to provide basic configuration to a formatter.
// Use NewFormatterConfig to initialize the FormatterConfig struct.
type FormatterConfig struct {
Raw bool
HMACAccessor bool
// nonPersistentSalt is used for obtaining a salt that is not persisted.
type nonPersistentSalt struct{}
// formatterConfig is used to provide basic configuration to a formatter.
// Use newFormatterConfig to initialize the formatterConfig struct.
type formatterConfig struct {
raw bool
hmacAccessor bool
// Vault lacks pagination in its APIs. As a result, certain list operations can return **very** large responses.
// The user's chosen audit sinks may experience difficulty consuming audit records that swell to tens of megabytes
@@ -61,55 +61,32 @@ type FormatterConfig struct {
// The elision replaces the values of the "keys" and "key_info" fields with an integer count of the number of
// entries. This allows even the elided audit logs to still be useful for answering questions like
// "Was any data returned?" or "How many records were listed?".
ElideListResponses bool
elideListResponses bool
// This should only ever be used in a testing context
OmitTime bool
omitTime bool
// The required/target format for the event (supported: JSONFormat and JSONxFormat).
RequiredFormat format
requiredFormat format
// headerFormatter specifies the formatter used for headers that existing in any incoming audit request.
headerFormatter HeaderFormatter
// Prefix specifies a Prefix that should be prepended to any formatted request or response before serialization.
Prefix string
// prefix specifies a prefix that should be prepended to any formatted request or response before serialization.
prefix string
}
// EntryFormatter should be used to format audit requests and responses.
// NOTE: Use NewEntryFormatter to initialize the EntryFormatter struct.
type EntryFormatter struct {
config FormatterConfig
// entryFormatter should be used to format audit requests and responses.
// NOTE: Use newEntryFormatter to initialize the entryFormatter struct.
type entryFormatter struct {
config formatterConfig
salter Salter
logger hclog.Logger
name string
}
// NewFormatterConfig should be used to create a FormatterConfig.
// Accepted options: WithElision, WithFormat, WithHMACAccessor, WithOmitTime, WithPrefix, WithRaw.
func NewFormatterConfig(headerFormatter HeaderFormatter, opt ...Option) (FormatterConfig, error) {
if headerFormatter == nil || reflect.ValueOf(headerFormatter).IsNil() {
return FormatterConfig{}, fmt.Errorf("header formatter is required: %w", ErrInvalidParameter)
}
opts, err := getOpts(opt...)
if err != nil {
return FormatterConfig{}, err
}
return FormatterConfig{
headerFormatter: headerFormatter,
ElideListResponses: opts.withElision,
HMACAccessor: opts.withHMACAccessor,
OmitTime: opts.withOmitTime,
Prefix: opts.withPrefix,
Raw: opts.withRaw,
RequiredFormat: opts.withFormat,
}, nil
}
// NewEntryFormatter should be used to create an EntryFormatter.
func NewEntryFormatter(name string, config FormatterConfig, salter Salter, logger hclog.Logger) (*EntryFormatter, error) {
// newEntryFormatter should be used to create an entryFormatter.
func newEntryFormatter(name string, config formatterConfig, salter Salter, logger hclog.Logger) (*entryFormatter, error) {
name = strings.TrimSpace(name)
if name == "" {
return nil, fmt.Errorf("name is required: %w", ErrInvalidParameter)
@@ -123,7 +100,7 @@ func NewEntryFormatter(name string, config FormatterConfig, salter Salter, logge
return nil, fmt.Errorf("cannot create a new audit formatter with nil logger: %w", ErrInvalidParameter)
}
return &EntryFormatter{
return &entryFormatter{
config: config,
salter: salter,
logger: logger,
@@ -132,18 +109,18 @@ func NewEntryFormatter(name string, config FormatterConfig, salter Salter, logge
}
// Reopen is a no-op for the formatter node.
func (*EntryFormatter) Reopen() error {
func (*entryFormatter) Reopen() error {
return nil
}
// Type describes the type of this node (formatter).
func (*EntryFormatter) Type() eventlogger.NodeType {
func (*entryFormatter) Type() eventlogger.NodeType {
return eventlogger.NodeTypeFormatter
}
// Process will attempt to parse the incoming event data into a corresponding
// audit Request/Response which is serialized to JSON/JSONx and stored within the event.
func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *eventlogger.Event, retErr error) {
func (f *entryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *eventlogger.Event, retErr error) {
// Return early if the context was cancelled, eventlogger will not carry on
// asking nodes to process, so any sink node in the pipeline won't be called.
select {
@@ -211,14 +188,14 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *
}
// Using 'any' as we have two different types that we can get back from either
// FormatRequest or FormatResponse, but the JSON encoder doesn't care about types.
// formatRequest or formatResponse, but the JSON encoder doesn't care about types.
var entry any
switch a.Subtype {
case RequestType:
entry, err = f.FormatRequest(ctx, data, a)
entry, err = f.formatRequest(ctx, data, a)
case ResponseType:
entry, err = f.FormatResponse(ctx, data, a)
entry, err = f.formatResponse(ctx, data, a)
default:
return nil, fmt.Errorf("unknown audit event subtype: %q", a.Subtype)
}
@@ -231,7 +208,7 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *
return nil, fmt.Errorf("unable to format %s: %w", a.Subtype, err)
}
if f.config.RequiredFormat == JSONxFormat {
if f.config.requiredFormat == JSONxFormat {
var err error
result, err = jsonx.EncodeJSONBytes(result)
if err != nil {
@@ -246,8 +223,8 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *
// don't support a prefix just sitting there.
// However, this would be a breaking change to how Vault currently works to
// include the prefix as part of the JSON object or XML document.
if f.config.Prefix != "" {
result = append([]byte(f.config.Prefix), result...)
if f.config.prefix != "" {
result = append([]byte(f.config.prefix), result...)
}
// Copy some properties from the event (and audit event) and store the
@@ -267,13 +244,13 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *
Payload: a2,
}
e2.FormattedAs(f.config.RequiredFormat.String(), result)
e2.FormattedAs(f.config.requiredFormat.String(), result)
return e2, nil
}
// FormatRequest attempts to format the specified logical.LogInput into a RequestEntry.
func (f *EntryFormatter) FormatRequest(ctx context.Context, in *logical.LogInput, provider timeProvider) (*RequestEntry, error) {
// formatRequest attempts to format the specified logical.LogInput into a RequestEntry.
func (f *entryFormatter) formatRequest(ctx context.Context, in *logical.LogInput, provider timeProvider) (*RequestEntry, error) {
switch {
case in == nil || in.Request == nil:
return nil, errors.New("request to request-audit a nil request")
@@ -293,14 +270,14 @@ func (f *EntryFormatter) FormatRequest(ctx context.Context, in *logical.LogInput
connState = in.Request.Connection.ConnState
}
if !f.config.Raw {
if !f.config.raw {
var err error
auth, err = HashAuth(ctx, f.salter, auth, f.config.HMACAccessor)
auth, err = HashAuth(ctx, f.salter, auth, f.config.hmacAccessor)
if err != nil {
return nil, err
}
req, err = HashRequest(ctx, f.salter, req, f.config.HMACAccessor, in.NonHMACReqDataKeys)
req, err = HashRequest(ctx, f.salter, req, f.config.hmacAccessor, in.NonHMACReqDataKeys)
if err != nil {
return nil, err
}
@@ -395,7 +372,7 @@ func (f *EntryFormatter) FormatRequest(ctx context.Context, in *logical.LogInput
reqEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second)
}
if !f.config.OmitTime {
if !f.config.omitTime {
// Use the time provider to supply the time for this entry.
reqEntry.Time = provider.formattedTime()
}
@@ -403,8 +380,8 @@ func (f *EntryFormatter) FormatRequest(ctx context.Context, in *logical.LogInput
return reqEntry, nil
}
// FormatResponse attempts to format the specified logical.LogInput into a ResponseEntry.
func (f *EntryFormatter) FormatResponse(ctx context.Context, in *logical.LogInput, provider timeProvider) (*ResponseEntry, error) {
// formatResponse attempts to format the specified logical.LogInput into a ResponseEntry.
func (f *entryFormatter) formatResponse(ctx context.Context, in *logical.LogInput, provider timeProvider) (*ResponseEntry, error) {
switch {
case f == nil:
return nil, errors.New("formatter is nil")
@@ -428,10 +405,10 @@ func (f *EntryFormatter) FormatResponse(ctx context.Context, in *logical.LogInpu
connState = in.Request.Connection.ConnState
}
elideListResponseData := f.config.ElideListResponses && req.Operation == logical.ListOperation
elideListResponseData := f.config.elideListResponses && req.Operation == logical.ListOperation
var respData map[string]interface{}
if f.config.Raw {
if f.config.raw {
// In the non-raw case, elision of list response data occurs inside HashResponse, to avoid redundant deep
// copies and hashing of data only to elide it later. In the raw case, we need to do it here.
if elideListResponseData && resp.Data != nil {
@@ -447,17 +424,17 @@ func (f *EntryFormatter) FormatResponse(ctx context.Context, in *logical.LogInpu
}
} else {
var err error
auth, err = HashAuth(ctx, f.salter, auth, f.config.HMACAccessor)
auth, err = HashAuth(ctx, f.salter, auth, f.config.hmacAccessor)
if err != nil {
return nil, err
}
req, err = HashRequest(ctx, f.salter, req, f.config.HMACAccessor, in.NonHMACReqDataKeys)
req, err = HashRequest(ctx, f.salter, req, f.config.hmacAccessor, in.NonHMACReqDataKeys)
if err != nil {
return nil, err
}
resp, err = HashResponse(ctx, f.salter, resp, f.config.HMACAccessor, in.NonHMACRespDataKeys, elideListResponseData)
resp, err = HashResponse(ctx, f.salter, resp, f.config.hmacAccessor, in.NonHMACRespDataKeys, elideListResponseData)
if err != nil {
return nil, err
}
@@ -616,7 +593,7 @@ func (f *EntryFormatter) FormatResponse(ctx context.Context, in *logical.LogInpu
respEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second)
}
if !f.config.OmitTime {
if !f.config.omitTime {
// Use the time provider to supply the time for this entry.
respEntry.Time = provider.formattedTime()
}
@@ -674,7 +651,7 @@ func parseVaultTokenFromJWT(token string) *string {
// determined it should apply to a particular request. The data map that is passed in must be a copy that is safe to
// modify in place, but need not be a full recursive deep copy, as only top-level keys are changed.
//
// See the documentation of the controlling option in FormatterConfig for more information on the purpose.
// See the documentation of the controlling option in formatterConfig for more information on the purpose.
func doElideListResponseData(data map[string]interface{}) {
for k, v := range data {
if k == "keys" {
@@ -689,9 +666,9 @@ func doElideListResponseData(data map[string]interface{}) {
}
}
// newTemporaryEntryFormatter creates a cloned EntryFormatter instance with a non-persistent Salter.
func newTemporaryEntryFormatter(n *EntryFormatter) *EntryFormatter {
return &EntryFormatter{
// newTemporaryEntryFormatter creates a cloned entryFormatter instance with a non-persistent Salter.
func newTemporaryEntryFormatter(n *entryFormatter) *entryFormatter {
return &entryFormatter{
salter: &nonPersistentSalt{},
config: n.config,
}