mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
refactored audit package (#25879)
This commit is contained in:
@@ -93,12 +93,16 @@ func (*EntryFormatter) Type() eventlogger.NodeType {
|
||||
func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *eventlogger.Event, retErr error) {
|
||||
const op = "audit.(EntryFormatter).Process"
|
||||
|
||||
// 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 {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// Perform validation on the event, then retrieve the underlying AuditEvent
|
||||
// and LogInput (from the AuditEvent Data).
|
||||
if e == nil {
|
||||
return nil, fmt.Errorf("%s: event is nil: %w", op, event.ErrInvalidParameter)
|
||||
}
|
||||
@@ -135,18 +139,14 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *
|
||||
return nil, fmt.Errorf("%s: unable to copy audit event data: %w", op, err)
|
||||
}
|
||||
|
||||
var headers map[string][]string
|
||||
if data.Request != nil && data.Request.Headers != nil {
|
||||
headers = data.Request.Headers
|
||||
}
|
||||
|
||||
if f.headerFormatter != nil {
|
||||
adjustedHeaders, err := f.headerFormatter.ApplyConfig(ctx, headers, f.salter)
|
||||
// Ensure that any headers in the request, are formatted as required, and are
|
||||
// only present if they have been configured to appear in the audit log.
|
||||
// e.g. via: /sys/config/auditing/request-headers/:name
|
||||
if f.headerFormatter != nil && data.Request != nil && data.Request.Headers != nil {
|
||||
data.Request.Headers, err = f.headerFormatter.ApplyConfig(ctx, data.Request.Headers, f.salter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to transform headers for auditing: %w", op, err)
|
||||
}
|
||||
|
||||
data.Request.Headers = adjustedHeaders
|
||||
}
|
||||
|
||||
// If the request contains a Server-Side Consistency Token (SSCT), and we
|
||||
@@ -156,32 +156,26 @@ func (f *EntryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *
|
||||
data.Auth.ClientToken = data.Request.InboundSSCToken
|
||||
}
|
||||
|
||||
var result []byte
|
||||
// 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.
|
||||
var entry any
|
||||
|
||||
switch a.Subtype {
|
||||
case RequestType:
|
||||
entry, err := f.FormatRequest(ctx, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to parse request from audit event: %w", op, err)
|
||||
}
|
||||
|
||||
result, err = jsonutil.EncodeJSON(entry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to format request: %w", op, err)
|
||||
}
|
||||
entry, err = f.FormatRequest(ctx, data)
|
||||
case ResponseType:
|
||||
entry, err := f.FormatResponse(ctx, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to parse response from audit event: %w", op, err)
|
||||
}
|
||||
|
||||
result, err = jsonutil.EncodeJSON(entry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to format response: %w", op, err)
|
||||
}
|
||||
entry, err = f.FormatResponse(ctx, data)
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: unknown audit event subtype: %q", op, a.Subtype)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to parse %s from audit event: %w", op, a.Subtype.String(), err)
|
||||
}
|
||||
|
||||
result, err := jsonutil.EncodeJSON(entry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: unable to format %s: %w", op, a.Subtype.String(), err)
|
||||
}
|
||||
|
||||
if f.config.RequiredFormat == JSONxFormat {
|
||||
var err error
|
||||
|
||||
@@ -231,14 +231,14 @@ func TestEntryFormatter_Process(t *testing.T) {
|
||||
}{
|
||||
"json-request-no-data": {
|
||||
IsErrorExpected: true,
|
||||
ExpectedErrorMessage: "audit.(EntryFormatter).Process: cannot audit event (AuditRequest) with no data: invalid parameter",
|
||||
ExpectedErrorMessage: "audit.(EntryFormatter).Process: cannot audit event (request) with no data: invalid parameter",
|
||||
Subtype: RequestType,
|
||||
RequiredFormat: JSONFormat,
|
||||
Data: nil,
|
||||
},
|
||||
"json-response-no-data": {
|
||||
IsErrorExpected: true,
|
||||
ExpectedErrorMessage: "audit.(EntryFormatter).Process: cannot audit event (AuditResponse) with no data: invalid parameter",
|
||||
ExpectedErrorMessage: "audit.(EntryFormatter).Process: cannot audit event (response) with no data: invalid parameter",
|
||||
Subtype: ResponseType,
|
||||
RequiredFormat: JSONFormat,
|
||||
Data: nil,
|
||||
@@ -287,14 +287,14 @@ func TestEntryFormatter_Process(t *testing.T) {
|
||||
},
|
||||
"jsonx-request-no-data": {
|
||||
IsErrorExpected: true,
|
||||
ExpectedErrorMessage: "audit.(EntryFormatter).Process: cannot audit event (AuditRequest) with no data: invalid parameter",
|
||||
ExpectedErrorMessage: "audit.(EntryFormatter).Process: cannot audit event (request) with no data: invalid parameter",
|
||||
Subtype: RequestType,
|
||||
RequiredFormat: JSONxFormat,
|
||||
Data: nil,
|
||||
},
|
||||
"jsonx-response-no-data": {
|
||||
IsErrorExpected: true,
|
||||
ExpectedErrorMessage: "audit.(EntryFormatter).Process: cannot audit event (AuditResponse) with no data: invalid parameter",
|
||||
ExpectedErrorMessage: "audit.(EntryFormatter).Process: cannot audit event (response) with no data: invalid parameter",
|
||||
Subtype: ResponseType,
|
||||
RequiredFormat: JSONxFormat,
|
||||
Data: nil,
|
||||
|
||||
@@ -140,5 +140,17 @@ func (t subtype) MetricTag() string {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -332,3 +332,39 @@ func TestAuditEvent_Subtype_MetricTag(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAuditEvent_Subtype_String is used to ensure that we get the string value
|
||||
// we expect for a subtype when it is used with the Stringer interface.
|
||||
// e.g. an AuditRequest subtype is 'request'
|
||||
func TestAuditEvent_Subtype_String(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
}{
|
||||
"request": {
|
||||
input: "AuditRequest",
|
||||
expectedOutput: "request",
|
||||
},
|
||||
"response": {
|
||||
input: "AuditResponse",
|
||||
expectedOutput: "response",
|
||||
},
|
||||
"non-validated": {
|
||||
input: "juan",
|
||||
expectedOutput: "juan",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
name := name
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
st := subtype(tc.input)
|
||||
require.Equal(t, tc.expectedOutput, st.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
152
audit/types.go
152
audit/types.go
@@ -12,6 +12,36 @@ import (
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
)
|
||||
|
||||
// Backend interface must be implemented for an audit
|
||||
// mechanism to be made available. Audit backends can be enabled to
|
||||
// sink information to different backends such as logs, file, databases,
|
||||
// or other external services.
|
||||
type Backend interface {
|
||||
// Salter interface must be implemented by anything implementing Backend.
|
||||
Salter
|
||||
|
||||
// The PipelineReader interface allows backends to surface information about their
|
||||
// nodes for node and pipeline registration.
|
||||
event.PipelineReader
|
||||
|
||||
// IsFallback can be used to determine if this audit backend device is intended to
|
||||
// be used as a fallback to catch all events that are not written when only using
|
||||
// filtered pipelines.
|
||||
IsFallback() bool
|
||||
|
||||
// LogTestMessage is used to check an audit backend before adding it
|
||||
// permanently. It should attempt to synchronously log the given test
|
||||
// message, WITHOUT using the normal Salt (which would require a storage
|
||||
// operation on creation, which is currently disallowed.)
|
||||
LogTestMessage(context.Context, *logical.LogInput) error
|
||||
|
||||
// Reload is called on SIGHUP for supporting backends.
|
||||
Reload(context.Context) error
|
||||
|
||||
// Invalidate is called for path invalidation
|
||||
Invalidate(context.Context)
|
||||
}
|
||||
|
||||
// Salter is an interface that provides a way to obtain a Salt for hashing.
|
||||
type Salter interface {
|
||||
// Salt returns a non-nil salt or an error.
|
||||
@@ -73,86 +103,86 @@ type FormatterConfig struct {
|
||||
|
||||
// RequestEntry is the structure of a request audit log entry.
|
||||
type RequestEntry struct {
|
||||
Time string `json:"time,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Auth *Auth `json:"auth,omitempty"`
|
||||
Request *Request `json:"request,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
ForwardedFrom string `json:"forwarded_from,omitempty"` // Populated in Enterprise when a request is forwarded
|
||||
Request *Request `json:"request,omitempty"`
|
||||
Time string `json:"time,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// ResponseEntry is the structure of a response audit log entry.
|
||||
type ResponseEntry struct {
|
||||
Time string `json:"time,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Auth *Auth `json:"auth,omitempty"`
|
||||
Request *Request `json:"request,omitempty"`
|
||||
Response *Response `json:"response,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Forwarded bool `json:"forwarded,omitempty"`
|
||||
Time string `json:"time,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Request *Request `json:"request,omitempty"`
|
||||
Response *Response `json:"response,omitempty"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
ClientCertificateSerialNumber string `json:"client_certificate_serial_number,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
ReplicationCluster string `json:"replication_cluster,omitempty"`
|
||||
Operation logical.Operation `json:"operation,omitempty"`
|
||||
MountPoint string `json:"mount_point,omitempty"`
|
||||
MountType string `json:"mount_type,omitempty"`
|
||||
MountAccessor string `json:"mount_accessor,omitempty"`
|
||||
MountRunningVersion string `json:"mount_running_version,omitempty"`
|
||||
MountRunningSha256 string `json:"mount_running_sha256,omitempty"`
|
||||
MountClass string `json:"mount_class,omitempty"`
|
||||
MountIsExternalPlugin bool `json:"mount_is_external_plugin,omitempty"`
|
||||
ClientToken string `json:"client_token,omitempty"`
|
||||
ClientTokenAccessor string `json:"client_token_accessor,omitempty"`
|
||||
Namespace *Namespace `json:"namespace,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Headers map[string][]string `json:"headers,omitempty"`
|
||||
MountAccessor string `json:"mount_accessor,omitempty"`
|
||||
MountClass string `json:"mount_class,omitempty"`
|
||||
MountPoint string `json:"mount_point,omitempty"`
|
||||
MountType string `json:"mount_type,omitempty"`
|
||||
MountRunningVersion string `json:"mount_running_version,omitempty"`
|
||||
MountRunningSha256 string `json:"mount_running_sha256,omitempty"`
|
||||
MountIsExternalPlugin bool `json:"mount_is_external_plugin,omitempty"`
|
||||
Namespace *Namespace `json:"namespace,omitempty"`
|
||||
Operation logical.Operation `json:"operation,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
PolicyOverride bool `json:"policy_override,omitempty"`
|
||||
RemoteAddr string `json:"remote_address,omitempty"`
|
||||
RemotePort int `json:"remote_port,omitempty"`
|
||||
WrapTTL int `json:"wrap_ttl,omitempty"`
|
||||
Headers map[string][]string `json:"headers,omitempty"`
|
||||
ClientCertificateSerialNumber string `json:"client_certificate_serial_number,omitempty"`
|
||||
ReplicationCluster string `json:"replication_cluster,omitempty"`
|
||||
RequestURI string `json:"request_uri,omitempty"`
|
||||
WrapTTL int `json:"wrap_ttl,omitempty"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Auth *Auth `json:"auth,omitempty"`
|
||||
MountPoint string `json:"mount_point,omitempty"`
|
||||
MountType string `json:"mount_type,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Headers map[string][]string `json:"headers,omitempty"`
|
||||
MountAccessor string `json:"mount_accessor,omitempty"`
|
||||
MountRunningVersion string `json:"mount_running_plugin_version,omitempty"`
|
||||
MountRunningSha256 string `json:"mount_running_sha256,omitempty"`
|
||||
MountClass string `json:"mount_class,omitempty"`
|
||||
MountIsExternalPlugin bool `json:"mount_is_external_plugin,omitempty"`
|
||||
Secret *Secret `json:"secret,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Warnings []string `json:"warnings,omitempty"`
|
||||
MountPoint string `json:"mount_point,omitempty"`
|
||||
MountRunningSha256 string `json:"mount_running_sha256,omitempty"`
|
||||
MountRunningVersion string `json:"mount_running_plugin_version,omitempty"`
|
||||
MountType string `json:"mount_type,omitempty"`
|
||||
Redirect string `json:"redirect,omitempty"`
|
||||
Secret *Secret `json:"secret,omitempty"`
|
||||
WrapInfo *ResponseWrapInfo `json:"wrap_info,omitempty"`
|
||||
Headers map[string][]string `json:"headers,omitempty"`
|
||||
Warnings []string `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
ClientToken string `json:"client_token,omitempty"`
|
||||
Accessor string `json:"accessor,omitempty"`
|
||||
ClientToken string `json:"client_token,omitempty"`
|
||||
DisplayName string `json:"display_name,omitempty"`
|
||||
Policies []string `json:"policies,omitempty"`
|
||||
TokenPolicies []string `json:"token_policies,omitempty"`
|
||||
IdentityPolicies []string `json:"identity_policies,omitempty"`
|
||||
ExternalNamespacePolicies map[string][]string `json:"external_namespace_policies,omitempty"`
|
||||
NoDefaultPolicy bool `json:"no_default_policy,omitempty"`
|
||||
PolicyResults *PolicyResults `json:"policy_results,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
NumUses int `json:"num_uses,omitempty"`
|
||||
RemainingUses int `json:"remaining_uses,omitempty"`
|
||||
EntityID string `json:"entity_id,omitempty"`
|
||||
EntityCreated bool `json:"entity_created,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
TokenTTL int64 `json:"token_ttl,omitempty"`
|
||||
EntityID string `json:"entity_id,omitempty"`
|
||||
ExternalNamespacePolicies map[string][]string `json:"external_namespace_policies,omitempty"`
|
||||
IdentityPolicies []string `json:"identity_policies,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
NoDefaultPolicy bool `json:"no_default_policy,omitempty"`
|
||||
NumUses int `json:"num_uses,omitempty"`
|
||||
Policies []string `json:"policies,omitempty"`
|
||||
PolicyResults *PolicyResults `json:"policy_results,omitempty"`
|
||||
RemainingUses int `json:"remaining_uses,omitempty"`
|
||||
TokenPolicies []string `json:"token_policies,omitempty"`
|
||||
TokenIssueTime string `json:"token_issue_time,omitempty"`
|
||||
TokenTTL int64 `json:"token_ttl,omitempty"`
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
}
|
||||
|
||||
type PolicyResults struct {
|
||||
@@ -172,11 +202,11 @@ type Secret struct {
|
||||
}
|
||||
|
||||
type ResponseWrapInfo struct {
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Accessor string `json:"accessor,omitempty"`
|
||||
CreationTime string `json:"creation_time,omitempty"`
|
||||
CreationPath string `json:"creation_path,omitempty"`
|
||||
CreationTime string `json:"creation_time,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
TTL int `json:"ttl,omitempty"`
|
||||
WrappedAccessor string `json:"wrapped_accessor,omitempty"`
|
||||
}
|
||||
|
||||
@@ -188,36 +218,6 @@ type Namespace struct {
|
||||
// nonPersistentSalt is used for obtaining a salt that is not persisted.
|
||||
type nonPersistentSalt struct{}
|
||||
|
||||
// Backend interface must be implemented for an audit
|
||||
// mechanism to be made available. Audit backends can be enabled to
|
||||
// sink information to different backends such as logs, file, databases,
|
||||
// or other external services.
|
||||
type Backend interface {
|
||||
// Salter interface must be implemented by anything implementing Backend.
|
||||
Salter
|
||||
|
||||
// The PipelineReader interface allows backends to surface information about their
|
||||
// nodes for node and pipeline registration.
|
||||
event.PipelineReader
|
||||
|
||||
// IsFallback can be used to determine if this audit backend device is intended to
|
||||
// be used as a fallback to catch all events that are not written when only using
|
||||
// filtered pipelines.
|
||||
IsFallback() bool
|
||||
|
||||
// LogTestMessage is used to check an audit backend before adding it
|
||||
// permanently. It should attempt to synchronously log the given test
|
||||
// message, WITHOUT using the normal Salt (which would require a storage
|
||||
// operation on creation, which is currently disallowed.)
|
||||
LogTestMessage(context.Context, *logical.LogInput) error
|
||||
|
||||
// Reload is called on SIGHUP for supporting backends.
|
||||
Reload(context.Context) error
|
||||
|
||||
// Invalidate is called for path invalidation
|
||||
Invalidate(context.Context)
|
||||
}
|
||||
|
||||
// BackendConfig contains configuration parameters used in the factory func to
|
||||
// instantiate audit backends
|
||||
type BackendConfig struct {
|
||||
|
||||
Reference in New Issue
Block a user