mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 02:27:02 +02:00
* add escape hatch to use feature flag for reversion of audit behavior * Setup pipeline which ends with a NoopSink * explicitly call out old way of running test * old behavior for audit trail tests * More manual forcing of tests to legacy audit system * Add NOTE: to suggest that the feature flag is temporary
320 lines
14 KiB
Go
320 lines
14 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package audit
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/hashicorp/eventlogger"
|
|
"github.com/hashicorp/vault/sdk/helper/salt"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
// Audit subtypes.
|
|
const (
|
|
RequestType subtype = "AuditRequest"
|
|
ResponseType subtype = "AuditResponse"
|
|
)
|
|
|
|
// Audit formats.
|
|
const (
|
|
JSONFormat format = "json"
|
|
JSONxFormat format = "jsonx"
|
|
)
|
|
|
|
// version defines the version of audit events.
|
|
const version = "v0.1"
|
|
|
|
// subtype defines the type of audit event.
|
|
type subtype string
|
|
|
|
// format defines types of format audit events support.
|
|
type format string
|
|
|
|
// 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.
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Data *logical.LogInput `json:"data"`
|
|
}
|
|
|
|
// Option is how options are passed as arguments.
|
|
type Option func(*options) error
|
|
|
|
// options are used to represent configuration for a audit related nodes.
|
|
type options struct {
|
|
withID string
|
|
withNow time.Time
|
|
withSubtype subtype
|
|
withFormat format
|
|
withPrefix string
|
|
withRaw bool
|
|
withElision bool
|
|
withOmitTime bool
|
|
withHMACAccessor bool
|
|
withHeaderFormatter HeaderFormatter
|
|
}
|
|
|
|
// 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.
|
|
Salt(context.Context) (*salt.Salt, error)
|
|
}
|
|
|
|
// Formatter is an interface that is responsible for formatting a request/response into some format.
|
|
// It is recommended that you pass data through Hash prior to formatting it.
|
|
type Formatter interface {
|
|
// FormatRequest formats the logical.LogInput into an RequestEntry.
|
|
FormatRequest(context.Context, *logical.LogInput) (*RequestEntry, error)
|
|
// FormatResponse formats the logical.LogInput into an ResponseEntry.
|
|
FormatResponse(context.Context, *logical.LogInput) (*ResponseEntry, error)
|
|
}
|
|
|
|
// Writer is an interface that provides a way to write request and response audit entries.
|
|
// Formatters write their output to an io.Writer.
|
|
type Writer interface {
|
|
// WriteRequest writes the request entry to the writer or returns an error.
|
|
WriteRequest(io.Writer, *RequestEntry) error
|
|
// WriteResponse writes the response entry to the writer or returns an error.
|
|
WriteResponse(io.Writer, *ResponseEntry) error
|
|
}
|
|
|
|
// HeaderFormatter is an interface defining the methods of the
|
|
// vault.AuditedHeadersConfig structure needed in this package.
|
|
type HeaderFormatter interface {
|
|
// ApplyConfig returns a map of header values that consists of the
|
|
// intersection of the provided set of header values with a configured
|
|
// set of headers and will hash headers that have been configured as such.
|
|
ApplyConfig(context.Context, map[string][]string, Salter) (map[string][]string, error)
|
|
}
|
|
|
|
// EntryFormatter should be used to format audit requests and responses.
|
|
type EntryFormatter struct {
|
|
salter Salter
|
|
headerFormatter HeaderFormatter
|
|
config FormatterConfig
|
|
prefix string
|
|
}
|
|
|
|
// EntryFormatterWriter should be used to format and write out audit requests and responses.
|
|
type EntryFormatterWriter struct {
|
|
Formatter
|
|
Writer
|
|
config FormatterConfig
|
|
}
|
|
|
|
// 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
|
|
// of JSON. The responses of list operations are typically not very interesting, as they are mostly lists of keys,
|
|
// or, even when they include a "key_info" field, are not returning confidential information. They become even less
|
|
// interesting once HMAC-ed by the audit system.
|
|
//
|
|
// Some example Vault "list" operations that are prone to becoming very large in an active Vault installation are:
|
|
// auth/token/accessors/
|
|
// identity/entity/id/
|
|
// identity/entity-alias/id/
|
|
// pki/certs/
|
|
//
|
|
// This option exists to provide such users with the option to have response data elided from audit logs, only when
|
|
// the operation type is "list". For added safety, the elision only applies to the "keys" and "key_info" fields
|
|
// within the response data - these are conventionally the only fields present in a list response - see
|
|
// logical.ListResponse, and logical.ListResponseWithInfo. However, other fields are technically possible if a
|
|
// plugin author writes unusual code, and these will be preserved in the audit log even with this option enabled.
|
|
// 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
|
|
|
|
// This should only ever be used in a testing context
|
|
OmitTime bool
|
|
|
|
// The required/target format for the event (supported: JSONFormat and JSONxFormat).
|
|
RequiredFormat format
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
type Request struct {
|
|
ID string `json:"id,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"`
|
|
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"`
|
|
}
|
|
|
|
type Response struct {
|
|
Auth *Auth `json:"auth,omitempty"`
|
|
MountPoint string `json:"mount_point,omitempty"`
|
|
MountType string `json:"mount_type,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"`
|
|
Redirect string `json:"redirect,omitempty"`
|
|
WrapInfo *ResponseWrapInfo `json:"wrap_info,omitempty"`
|
|
Headers map[string][]string `json:"headers,omitempty"`
|
|
}
|
|
|
|
type Auth struct {
|
|
ClientToken string `json:"client_token,omitempty"`
|
|
Accessor string `json:"accessor,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"`
|
|
TokenIssueTime string `json:"token_issue_time,omitempty"`
|
|
}
|
|
|
|
type PolicyResults struct {
|
|
Allowed bool `json:"allowed"`
|
|
GrantingPolicies []PolicyInfo `json:"granting_policies,omitempty"`
|
|
}
|
|
|
|
type PolicyInfo struct {
|
|
Name string `json:"name,omitempty"`
|
|
NamespaceId string `json:"namespace_id,omitempty"`
|
|
NamespacePath string `json:"namespace_path,omitempty"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type Secret struct {
|
|
LeaseID string `json:"lease_id,omitempty"`
|
|
}
|
|
|
|
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"`
|
|
WrappedAccessor string `json:"wrapped_accessor,omitempty"`
|
|
}
|
|
|
|
type Namespace struct {
|
|
ID string `json:"id,omitempty"`
|
|
Path string `json:"path,omitempty"`
|
|
}
|
|
|
|
// 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
|
|
|
|
// LogRequest is used to synchronously log a request. This is done after the
|
|
// request is authorized but before the request is executed. The arguments
|
|
// MUST not be modified in any way. They should be deep copied if this is
|
|
// a possibility.
|
|
LogRequest(context.Context, *logical.LogInput) error
|
|
|
|
// LogResponse is used to synchronously log a response. This is done after
|
|
// the request is processed but before the response is sent. The arguments
|
|
// MUST not be modified in any way. They should be deep copied if this is
|
|
// a possibility.
|
|
LogResponse(context.Context, *logical.LogInput) error
|
|
|
|
// 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, map[string]string) error
|
|
|
|
// Reload is called on SIGHUP for supporting backends.
|
|
Reload(context.Context) error
|
|
|
|
// Invalidate is called for path invalidation
|
|
Invalidate(context.Context)
|
|
|
|
// RegisterNodesAndPipeline provides an eventlogger.Broker pointer so that
|
|
// the Backend can call its RegisterNode and RegisterPipeline methods with
|
|
// the nodes and the pipeline that were created in the corresponding
|
|
// Factory function.
|
|
RegisterNodesAndPipeline(*eventlogger.Broker, string) error
|
|
}
|
|
|
|
// BackendConfig contains configuration parameters used in the factory func to
|
|
// instantiate audit backends
|
|
type BackendConfig struct {
|
|
// The view to store the salt
|
|
SaltView logical.Storage
|
|
|
|
// The salt config that should be used for any secret obfuscation
|
|
SaltConfig *salt.Config
|
|
|
|
// Config is the opaque user configuration provided when mounting
|
|
Config map[string]string
|
|
|
|
// MountPath is the path where this Backend is mounted
|
|
MountPath string
|
|
}
|
|
|
|
// Factory is the factory function to create an audit backend.
|
|
type Factory func(context.Context, *BackendConfig, bool, HeaderFormatter) (Backend, error)
|