vault/audit/event.go

185 lines
4.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package audit
import (
"fmt"
"strings"
"time"
"github.com/hashicorp/vault/internal/observability/event"
"github.com/hashicorp/vault/sdk/logical"
)
// version defines the version of audit events.
const version = "v0.1"
// Audit subtypes.
const (
RequestType subtype = "AuditRequest"
ResponseType subtype = "AuditResponse"
)
// Audit formats.
const (
jsonFormat format = "json"
jsonxFormat format = "jsonx"
)
// Check AuditEvent implements the timeProvider at compile time.
var _ timeProvider = (*Event)(nil)
// Event is the audit event.
type Event struct {
ID string `json:"id"`
Version string `json:"version"`
Subtype subtype `json:"subtype"` // the subtype of the audit event.
Timestamp time.Time `json:"timestamp"`
Data *logical.LogInput `json:"data"`
prov timeProvider
}
// setTimeProvider can be used to set a specific time provider which is used when
// creating an entry.
// NOTE: This is primarily used for testing to supply a known time value.
func (a *Event) setTimeProvider(t timeProvider) {
a.prov = t
}
// timeProvider returns a configured time provider, or the default if not set.
func (a *Event) timeProvider() timeProvider {
if a.prov == nil {
return a
}
return a.prov
}
// format defines types of format audit events support.
type format string
// subtype defines the type of audit event.
type subtype string
// newEvent should be used to create an audit event. The subtype field is needed
// for audit events. It will generate an ID if no ID is supplied. Supported
// options: withID, withNow.
func newEvent(s subtype, opt ...option) (*Event, error) {
// Get the default options
opts, err := getOpts(opt...)
if err != nil {
return nil, err
}
if opts.withID == "" {
var err error
opts.withID, err = event.NewID(string(event.AuditType))
if err != nil {
return nil, fmt.Errorf("error creating ID for event: %w", err)
}
}
audit := &Event{
ID: opts.withID,
Timestamp: opts.withNow,
Version: version,
Subtype: s,
}
if err := audit.validate(); err != nil {
return nil, err
}
return audit, nil
}
// validate attempts to ensure the audit event in its present state is valid.
func (a *Event) validate() error {
if a == nil {
return fmt.Errorf("event is nil: %w", ErrInvalidParameter)
}
if a.ID == "" {
return fmt.Errorf("missing ID: %w", ErrInvalidParameter)
}
if a.Version != version {
return fmt.Errorf("event version unsupported: %w", ErrInvalidParameter)
}
if a.Timestamp.IsZero() {
return fmt.Errorf("event timestamp cannot be the zero time instant: %w", ErrInvalidParameter)
}
err := a.Subtype.validate()
if err != nil {
return err
}
return nil
}
// validate ensures that subtype is one of the set of allowed event subtypes.
func (t subtype) validate() error {
switch t {
case RequestType, ResponseType:
return nil
default:
return fmt.Errorf("invalid event subtype %q: %w", t, ErrInvalidParameter)
}
}
// validate ensures that format is one of the set of allowed event formats.
func (f format) validate() error {
switch f {
case jsonFormat, jsonxFormat:
return nil
default:
return fmt.Errorf("invalid format %q: %w", f, ErrInvalidParameter)
}
}
// String returns the string version of a format.
func (f format) String() string {
return string(f)
}
// MetricTag returns a tag corresponding to this subtype to include in metrics.
// If a tag cannot be found the value is returned 'as-is' in string format.
func (t subtype) MetricTag() string {
switch t {
case RequestType:
return "log_request"
case ResponseType:
return "log_response"
}
return t.String()
}
// String returns the subtype as a human-readable string.
func (t subtype) String() string {
switch t {
case RequestType:
return "request"
case ResponseType:
return "response"
}
return string(t)
}
// formattedTime returns the UTC time the AuditEvent was created in the RFC3339Nano
// format (which removes trailing zeros from the seconds field).
func (a *Event) formattedTime() string {
return a.Timestamp.UTC().Format(time.RFC3339Nano)
}
// isValidFormat provides a means to validate whether the supplied format is valid.
// Examples of valid formats are JSON and JSONx.
func isValidFormat(v string) bool {
err := format(strings.TrimSpace(strings.ToLower(v))).validate()
return err == nil
}