Security context
Medium· 4.5GHSA-vgh3-mwxq-rcp8 CVE-2024-0831CWE-532Published Feb 1, 2024

Hashicorp Vault may expose sensitive log information

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

1.15.0 → fixed in 1.15.5

Details

Vault and Vault Enterprise (“Vault”) may expose sensitive information when enabling an audit device which specifies the `log_raw` option, which may log sensitive information to other audit devices, regardless of whether they are configured to use `log_raw`

The fix

VAULT-23122: Audit fix for 'log_raw' issue (#24968) (#24974)

hc-github-team-secure-vault-core· Jan 22, 2024, 12:46 PM+351742a72f2a8a5
audit/entry_formatter.go+20 4
@@ -80,7 +80,7 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (*ev
return nil, fmt.Errorf("%s: event is nil: %w", op, event.ErrInvalidParameter)
}
- a, ok := e.Payload.(*auditEvent)
+ a, ok := e.Payload.(*AuditEvent)
if !ok {
return nil, fmt.Errorf("%s: cannot parse event payload: %w", op, event.ErrInvalidParameter)
}
@@ -155,10 +155,26 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (*ev
result = append([]byte(f.prefix), result...)
}
- // Store the final format.
- e.FormattedAs(f.config.RequiredFormat.String(), result)
+ // Copy some properties from the event (and audit event) and store the
+ // format for the next (sink) node to Process.
+ a2 := &AuditEvent{
+ ID: a.ID,
+ Version: a.Version,
+ Subtype: a.Subtype,
+ Timestamp: a.Timestamp,
+ Data: data, // Use the cloned data here rather than a pointer to the original.
+ }
+
+ e2 := &eventlogger.Event{
+ Type: e.Type,
+ CreatedAt: e.CreatedAt,
+ Formatted: make(map[string][]byte), // we are about to set this ourselves.
+ Payload: a2,
+ }
+
+ e2.FormattedAs(f.config.RequiredFormat.String(), result)
- return e, nil
+ return e2, nil
}
// FormatRequest attempts to format the specified logical.LogInput into a RequestEntry.
audit/entry_formatter_test.go+302 43
@@ -5,49 +5,23 @@ package audit
import (
"context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "strings"
"testing"
"time"
- "github.com/hashicorp/vault/internal/observability/event"
-
+ "github.com/hashicorp/eventlogger"
"github.com/hashicorp/vault/helper/namespace"
-
+ "github.com/hashicorp/vault/internal/observability/event"
+ "github.com/hashicorp/vault/sdk/helper/jsonutil"
+ "github.com/hashicorp/vault/sdk/helper/salt"
"github.com/hashicorp/vault/sdk/logical"
-
- "github.com/hashicorp/eventlogger"
+ "github.com/mitchellh/copystructure"
"github.com/stretchr/testify/require"
)
-// fakeEvent will return a new fake event containing audit data based on the
-// specified subtype, format and logical.LogInput.
-func fakeEvent(tb testing.TB, subtype subtype, format format, input *logical.LogInput) *eventlogger.Event {
- tb.Helper()
-
- date := time.Date(2023, time.July, 11, 15, 49, 10, 0o0, time.Local)
-
- auditEvent, err := NewEvent(subtype,
- WithID("123"),
- WithNow(date),
- )
- require.NoError(tb, err)
- require.NotNil(tb, auditEvent)
- require.Equal(tb, "123", auditEvent.ID)
- require.Equal(tb, "v0.1", auditEvent.Version)
- require.Equal(tb, subtype, auditEvent.Subtype)
- require.Equal(tb, date, auditEvent.Timestamp)
-
- auditEvent.Data = input
-
- e := &eventlogger.Event{
- Type: eventlogger.EventType(event.AuditType),
- CreatedAt: auditEvent.Timestamp,
- Formatted: make(map[string][]byte),
- Payload: auditEvent,
- }
-
- return e
-}
-
// TestNewEntryFormatter ensures we can create new EntryFormatter structs.
func TestNewEntryFormatter(t *testing.T) {
tests := map[string]struct {
@@ -297,7 +271,7 @@ func TestEntryFormatter_Process(t *testing.T) {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
- e := fakeEvent(t, tc.Subtype, tc.RequiredFormat, tc.Data)
+ e := fakeEvent(t, tc.Subtype, tc.Data)
require.NotNil(t, e)
ss := newStaticSalt(t)
@@ -317,18 +291,16 @@ func TestEntryFormatter_Process(t *testing.T) {
}
processed, err := f.Process(ctx, e)
- b, found := e.Format(string(tc.RequiredFormat))
switch {
case tc.IsErrorExpected:
require.Error(t, err)
require.EqualError(t, err, tc.ExpectedErrorMessage)
require.Nil(t, processed)
- require.False(t, found)
- require.Nil(t, b)
default:
require.NoError(t, err)
require.NotNil(t, processed)
+ b, found := processed.Format(string(tc.RequiredFormat))
require.True(t, found)
require.NotNil(t, b)
}
@@ -381,20 +353,413 @@ func BenchmarkAuditFileSink_Process(b *testing.B) {
require.NotNil(b, sink)
// Generate the event
- event := fakeEvent(b, RequestType, JSONFormat, in)
- require.NotNil(b, event)
+ e := fakeEvent(b, RequestType, in)
+ require.NotNil(b, e)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
- event, err = formatter.Process(ctx, event)
+ e2, err := formatter.Process(ctx, e)
if err != nil {
panic(err)
}
- _, err := sink.Process(ctx, event)
+ _, err = sink.Process(ctx, e2)
if err != nil {
panic(err)
}
}
})
}
+
+// TestEntryFormatter_Process_JSON ensures that the JSON output we get matches what
+// we expect for the specified LogInput.
+func TestEntryFormatter_Process_JSON(t *testing.T) {
+ ss := newStaticSalt(t)
+
+ expectedResultStr := fmt.Sprintf(testFormatJSONReqBasicStrFmt, ss.salt.GetIdentifiedHMAC("foo"))
+
+ issueTime, _ := time.Parse(time.RFC3339, "2020-05-28T13:40:18-05:00")
+ cases := map[string]struct {
+ Auth *logical.Auth
+ Req *logical.Request
+ Err error
+ Prefix string
+ ExpectedStr string
+ }{
+ "auth, request": {
+ &logical.Auth{
+ ClientToken: "foo",
+ Accessor: "bar",
+ DisplayName: "testtoken",
+ EntityID: "foobarentity",
+ NoDefaultPolicy: true,
+ Policies: []string{"root"},
+ TokenType: logical.TokenTypeService,
+ LeaseOptions: logical.LeaseOptions{
+ TTL: time.Hour * 4,
+ IssueTime: issueTime,
+ },
+ },
+ &logical.Request{
+ Operation: logical.UpdateOperation,
+ Path: "/foo",
+ Connection: &logical.Connection{
+ RemoteAddr: "127.0.0.1",
+ },
+ WrapInfo: &logical.RequestWrapInfo{
+ TTL: 60 * time.Second,
+ },
+ Headers: map[string][]string{
+ "foo": {"bar"},
+ },
+ },
+ errors.New("this is an error"),
+ "",
+ expectedResultStr,
+ },
+ "auth, request with prefix": {
+ &logical.Auth{
+ ClientToken: "foo",
+ Accessor: "bar",
+ EntityID: "foobarentity",
+ DisplayName: "testtoken",
+ NoDefaultPolicy: true,
+ Policies: []string{"root"},
+ TokenType: logical.TokenTypeService,
+ LeaseOptions: logical.LeaseOptions{
+ TTL: time.Hour * 4,
+ IssueTime: issueTime,
+ },
+ },
+ &logical.Request{
+ Operation: logical.UpdateOperation,
+ Path: "/foo",
+ Connection: &logical.Connection{
+ RemoteAddr: "127.0.0.1",
+ },
+ WrapInfo: &logical.RequestWrapInfo{
+ TTL: 60 * time.Second,
+ },
+ Headers: map[string][]string{
+ "foo": {"bar"},
+ },
+ },
+ errors.New("this is an error"),
+ "@cee: ",
+ expectedResultStr,
+ },
+ }
+
+ for name, tc := range cases {
+ cfg, err := NewFormatterConfig(WithHMACAccessor(false))
+ require.NoError(t, err)
+ formatter, err := NewEntryFormatter(cfg, ss, WithPrefix(tc.Prefix))
+ require.NoError(t, err)
+
+ in := &logical.LogInput{
+ Auth: tc.Auth,
+ Request: tc.Req,
+ OuterErr: tc.Err,
+ }
+
+ // Create an audit event and more generic eventlogger.event to allow us
+ // to process (format).
+ auditEvent, err := NewEvent(RequestType)
+ require.NoError(t, err)
+ auditEvent.Data = in
+
+ e := &eventlogger.Event{
+ Type: eventlogger.EventType(event.AuditType.String()),
+ CreatedAt: time.Now(),
+ Formatted: make(map[string][]byte),
+ Payload: auditEvent,
+ }
+
+ e2, err := formatter.Process(namespace.RootContext(nil), e)
+ require.NoErrorf(t, err, "bad: %s\nerr: %s", name, err)
+
+ jsonBytes, ok := e2.Format(JSONFormat.String())
+ require.True(t, ok)
+ require.Positive(t, len(jsonBytes))
+
+ if !strings.HasPrefix(string(jsonBytes), tc.Prefix) {
+ t.Fatalf("no prefix: %s \n log: %s\nprefix: %s", name, expectedResultStr, tc.Prefix)
+ }
+
+ expectedJSON := new(RequestEntry)
+
+ if err := jsonutil.DecodeJSON([]byte(expectedResultStr), &expectedJSON); err != nil {
+ t.Fatalf("bad json: %s", err)
+ }
+ expectedJSON.Request.Namespace = &Namespace{ID: "root"}
+
+ actualJSON := new(RequestEntry)
+ if err := jsonutil.DecodeJSON(jsonBytes[len(tc.Prefix):], &actualJSON); err != nil {
+ t.Fatalf("bad json: %s", err)
+ }
+
+ expectedJSON.Time = actualJSON.Time
+
+ expectedBytes, err := json.Marshal(expectedJSON)
+ if err != nil {
+ t.Fatalf("unable to marshal json: %s", err)
+ }
+
+ if !strings.HasSuffix(strings.TrimSpace(string(jsonBytes)), string(expectedBytes)) {
+ t.Fatalf("bad: %s\nResult:\n\n%q\n\nExpected:\n\n%q", name, string(jsonBytes), string(expectedBytes))
+ }
+ }
+}
+
+// TestEntryFormatter_Process_JSONx ensures that the JSONx output we get matches what
+// we expect for the specified LogInput.
+func TestEntryFormatter_Process_JSONx(t *testing.T) {
+ s, err := salt.NewSalt(context.Background(), nil, nil)
+ require.NoError(t, err)
+ tempStaticSalt := &staticSalt{salt: s}
+
+ fooSalted := s.GetIdentifiedHMAC("foo")
+ issueTime, _ := time.Parse(time.RFC3339, "2020-05-28T13:40:18-05:00")
+
+ cases := map[string]struct {
+ Auth *logical.Auth
+ Req *logical.Request
+ Err error
+ Prefix string
+ Result string
+ ExpectedStr string
+ }{
+ "auth, request": {
+ &logical.Auth{
+ ClientToken: "foo",
+ Accessor: "bar",
+ DisplayName: "testtoken",
+ EntityID: "foobarentity",
+ NoDefaultPolicy: true,
+ Policies: []string{"root"},
+ TokenType: logical.TokenTypeService,
+ LeaseOptions: logical.LeaseOptions{
+ TTL: time.Hour * 4,
+ IssueTime: issueTime,
+ },
+ },
+ &logical.Request{
+ ID: "request",
+ ClientToken: "foo",
+ ClientTokenAccessor: "bar",
+ Operation: logical.UpdateOperation,
+ Path: "/foo",
+ Connection: &logical.Connection{
+ RemoteAddr: "127.0.0.1",
+ },
+ WrapInfo: &logical.RequestWrapInfo{
+ TTL: 60 * time.Second,
+ },
+ Headers: map[string][]string{
+ "foo": {"bar"},
+ },
+ PolicyOverride: true,
+ },
+ errors.New("this is an error"),
+ "",
+ "",
+ fmt.Sprintf(`<json:object name="auth"><json:string name="accessor">bar</json:string><json:string name="client_token">%s</json:string><json:string name="display_name">testtoken</json:string><json:string name="entity_id">foobarentity</json:string><json:boolean name="no_default_policy">true</json:boolean><json:array name="policies"><json:string>root</json:string></json:array><json:string name="token_issue_time">2020-05-28T13:40:18-05:00</json:string><json:number name="token_ttl">14400</json:number><json:string name="token_type">service</json:string></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token">%s</json:string><json:string name="client_token_accessor">bar</json:string><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id">request</json:string><json:object name="namespace"><json:string name="id">root</json:string></json:object><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:boolean name="policy_override">true</json:boolean><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`,
+ fooSalted, fooSalted),
+ },
+ "auth, request with prefix": {
+ &logical.Auth{
+ ClientToken: "foo",
+ Accessor: "bar",
+ DisplayName: "testtoken",
+ NoDefaultPolicy: true,
+ EntityID: "foobarentity",
+ Policies: []string{"root"},
+ TokenType: logical.TokenTypeService,
+ LeaseOptions: logical.LeaseOptions{
+ TTL: time.Hour * 4,
+ IssueTime: issueTime,
+ },
+ },
+ &logical.Request{
+ ID: "request",
+ ClientToken: "foo",
+ ClientTokenAccessor: "bar",
+ Operation: logical.UpdateOperation,
+ Path: "/foo",
+ Connection: &logical.Connection{
+ RemoteAddr: "127.0.0.1",
+ },
+ WrapInfo: &logical.RequestWrapInfo{
+ TTL: 60 * time.Second,
+ },
+ Headers: map[string][]string{
+ "foo": {"bar"},
+ },
+ PolicyOverride: true,
+ },
+ errors.New("this is an error"),
+ "",
+ "@cee: ",
+ fmt.Sprintf(`<json:object name="auth"><json:string name="accessor">bar</json:string><json:string name="client_token">%s</json:string><json:string name="display_name">testtoken</json:string><json:string name="entity_id">foobarentity</json:string><json:boolean name="no_default_policy">true</json:boolean><json:array name="policies"><json:string>root</json:string></json:array><json:string name="token_issue_time">2020-05-28T13:40:18-05:00</json:string><json:number name="token_ttl">14400</json:number><json:string name="token_type">service</json:string></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token">%s</json:string><json:string name="client_token_accessor">bar</json:string><json:object name="headers"><json:array name="foo"><json:string>bar</json:string></json:array></json:object><json:string name="id">request</json:string><json:object name="namespace"><json:string name="id">root</json:string></json:object><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:boolean name="policy_override">true</json:boolean><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`,
+ fooSalted, fooSalted),
+ },
+ }
+
+ for name, tc := range cases {
+ cfg, err := NewFormatterConfig(
+ WithOmitTime(true),
+ WithHMACAccessor(false),
+ WithFormat(JSONxFormat.String()),
+ )
+ require.NoError(t, err)
+ formatter, err := NewEntryFormatter(cfg, tempStaticSalt, WithPrefix(tc.Prefix))
+ require.NoError(t, err)
+ require.NotNil(t, formatter)
+
+ in := &logical.LogInput{
+ Auth: tc.Auth,
+ Request: tc.Req,
+ OuterErr: tc.Err,
+ }
+
+ // Create an audit event and more generic eventlogger.event to allow us
+ // to process (format).
+ auditEvent, err := NewEvent(RequestType)
+ require.NoError(t, err)
+ auditEvent.Data = in
+
+ e := &eventlogger.Event{
+ Type: eventlogger.EventType(event.AuditType.String()),
+ CreatedAt: time.Now(),
+ Formatted: make(map[string][]byte),
+ Payload: auditEvent,
+ }
+
+ e2, err := formatter.Process(namespace.RootContext(nil), e)
+ require.NoErrorf(t, err, "bad: %s\nerr: %s", name, err)
+
+ jsonxBytes, ok := e2.Format(JSONxFormat.String())
+ require.True(t, ok)
+ require.Positive(t, len(jsonxBytes))
+
+ if !strings.HasPrefix(string(jsonxBytes), tc.Prefix) {
+ t.Fatalf("no prefix: %s \n log: %s\nprefix: %s", name, tc.Result, tc.Prefix)
+ }
+
+ if !strings.HasSuffix(strings.TrimSpace(string(jsonxBytes)), string(tc.ExpectedStr)) {
+ t.Fatalf(
+ "bad: %s\nResult:\n\n%q\n\nExpected:\n\n%q",
+ name, strings.TrimSpace(string(jsonxBytes)), string(tc.ExpectedStr))
+ }
+ }
+}
+
+// TestEntryFormatter_Process_NoMutation tests that the event returned by an
+// EntryFormatter.Process method is not the same as the one that it accepted.
+func TestEntryFormatter_Process_NoMutation(t *testing.T) {
… diff truncated
audit/event.go+5 5
@@ -12,8 +12,8 @@ import (
// 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) (*auditEvent, error) {
- const op = "audit.newEvent"
+func NewEvent(s subtype, opt ...Option) (*AuditEvent, error) {
+ const op = "audit.NewEvent"
// Get the default options
opts, err := getOpts(opt...)
@@ -30,7 +30,7 @@ func NewEvent(s subtype, opt ...Option) (*auditEvent, error) {
}
}
- audit := &auditEvent{
+ audit := &AuditEvent{
ID: opts.withID,
Timestamp: opts.withNow,
Version: version,
@@ -44,8 +44,8 @@ func NewEvent(s subtype, opt ...Option) (*auditEvent, error) {
}
// validate attempts to ensure the audit event in its present state is valid.
-func (a *auditEvent) validate() error {
- const op = "audit.(auditEvent).validate"
+func (a *AuditEvent) validate() error {
+ const op = "audit.(AuditEvent).validate"
if a == nil {
return fmt.Errorf("%s: event is nil: %w", op, event.ErrInvalidParameter)
audit/event_test.go+16 16
@@ -29,21 +29,21 @@ func TestAuditEvent_new(t *testing.T) {
Subtype: subtype(""),
Format: format(""),
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.newEvent: audit.(auditEvent).validate: audit.(subtype).validate: '' is not a valid event subtype: invalid parameter",
+ ExpectedErrorMessage: "audit.NewEvent: audit.(AuditEvent).validate: audit.(subtype).validate: '' is not a valid event subtype: invalid parameter",
},
"empty-Option": {
Options: []Option{},
Subtype: subtype(""),
Format: format(""),
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.newEvent: audit.(auditEvent).validate: audit.(subtype).validate: '' is not a valid event subtype: invalid parameter",
+ ExpectedErrorMessage: "audit.NewEvent: audit.(AuditEvent).validate: audit.(subtype).validate: '' is not a valid event subtype: invalid parameter",
},
"bad-id": {
Options: []Option{WithID("")},
Subtype: ResponseType,
Format: JSONFormat,
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.newEvent: error applying options: id cannot be empty",
+ ExpectedErrorMessage: "audit.NewEvent: error applying options: id cannot be empty",
},
"good": {
Options: []Option{
@@ -108,22 +108,22 @@ func TestAuditEvent_new(t *testing.T) {
// TestAuditEvent_Validate exercises the validation for an audit event.
func TestAuditEvent_Validate(t *testing.T) {
tests := map[string]struct {
- Value *auditEvent
+ Value *AuditEvent
IsErrorExpected bool
ExpectedErrorMessage string
}{
"nil": {
Value: nil,
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.(auditEvent).validate: event is nil: invalid parameter",
+ ExpectedErrorMessage: "audit.(AuditEvent).validate: event is nil: invalid parameter",
},
"default": {
- Value: &auditEvent{},
+ Value: &AuditEvent{},
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.(auditEvent).validate: missing ID: invalid parameter",
+ ExpectedErrorMessage: "audit.(AuditEvent).validate: missing ID: invalid parameter",
},
"id-empty": {
- Value: &auditEvent{
+ Value: &AuditEvent{
ID: "",
Version: version,
Subtype: RequestType,
@@ -131,10 +131,10 @@ func TestAuditEvent_Validate(t *testing.T) {
Data: nil,
},
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.(auditEvent).validate: missing ID: invalid parameter",
+ ExpectedErrorMessage: "audit.(AuditEvent).validate: missing ID: invalid parameter",
},
"version-fiddled": {
- Value: &auditEvent{
+ Value: &AuditEvent{
ID: "audit_123",
Version: "magic-v2",
Subtype: RequestType,
@@ -142,10 +142,10 @@ func TestAuditEvent_Validate(t *testing.T) {
Data: nil,
},
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.(auditEvent).validate: event version unsupported: invalid parameter",
+ ExpectedErrorMessage: "audit.(AuditEvent).validate: event version unsupported: invalid parameter",
},
"subtype-fiddled": {
- Value: &auditEvent{
+ Value: &AuditEvent{
ID: "audit_123",
Version: version,
Subtype: subtype("moon"),
@@ -153,10 +153,10 @@ func TestAuditEvent_Validate(t *testing.T) {
Data: nil,
},
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.(auditEvent).validate: audit.(subtype).validate: 'moon' is not a valid event subtype: invalid parameter",
+ ExpectedErrorMessage: "audit.(AuditEvent).validate: audit.(subtype).validate: 'moon' is not a valid event subtype: invalid parameter",
},
"default-time": {
- Value: &auditEvent{
+ Value: &AuditEvent{
ID: "audit_123",
Version: version,
Subtype: ResponseType,
@@ -164,10 +164,10 @@ func TestAuditEvent_Validate(t *testing.T) {
Data: nil,
},
IsErrorExpected: true,
- ExpectedErrorMessage: "audit.(auditEvent).validate: event timestamp cannot be the zero time instant: invalid parameter",
+ ExpectedErrorMessage: "audit.(AuditEvent).validate: event timestamp cannot be the zero time instant: invalid parameter",
},
"valid": {
- Value: &auditEvent{
+ Value: &AuditEvent{
ID: "audit_123",
Version: version,
Subtype: ResponseType,
audit/sink_wrapper.go+2 2
@@ -12,7 +12,7 @@ import (
)
// SinkWrapper is a wrapper for any kind of Sink Node that processes events
-// containing an auditEvent payload.
+// containing an AuditEvent payload.
type SinkWrapper struct {
Name string
Sink eventlogger.Node
@@ -23,7 +23,7 @@ type SinkWrapper struct {
// once this method returns.
func (s *SinkWrapper) Process(ctx context.Context, e *eventlogger.Event) (*eventlogger.Event, error) {
defer func() {
- auditEvent, ok := e.Payload.(*auditEvent)
+ auditEvent, ok := e.Payload.(*AuditEvent)
if ok {
metrics.MeasureSince([]string{"audit", s.Name, auditEvent.Subtype.MetricTag()}, e.CreatedAt)
}
audit/types.go+2 2
@@ -35,8 +35,8 @@ type subtype string
// format defines types of format audit events support.
type format string
-// auditEvent is the audit event.
-type auditEvent struct {
+// AuditEvent is the audit event.
+type AuditEvent struct {
ID string `json:"id"`
Version string `json:"version"`
Subtype subtype `json:"subtype"` // the subtype of the audit event.
changelog/24968.txt+3 0
@@ -0,0 +1,3 @@
+```release-note:bug
+audit: Fix bug where use of 'log_raw' option could result in other devices logging raw audit data
+```
internal/observability/event/sink_socket.go+1 2
@@ -10,9 +10,8 @@ import (
"sync"
"time"
- "github.com/hashicorp/go-multierror"
-
"github.com/hashicorp/eventlogger"
+ "github.com/hashicorp/go-multierror"
)
var _ eventlogger.Node = (*SocketSink)(nil)
More files changed — see the full commit.

References