diff --git a/audit/entry_formatter_writer.go b/audit/entry_formatter_writer.go index 11cc4108b5..55861f7746 100644 --- a/audit/entry_formatter_writer.go +++ b/audit/entry_formatter_writer.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "strings" - "time" "github.com/hashicorp/vault/sdk/helper/salt" "github.com/hashicorp/vault/sdk/logical" @@ -20,91 +19,6 @@ var ( _ Writer = (*EntryFormatterWriter)(nil) ) -// 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 -} - -// EntryFormatter should be used to format audit entries. -type EntryFormatter struct { - salter Salter - config FormatterConfig - prefix string -} - -// EntryFormatterWriter should be used to format and write out audit entries. -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 audit entry (supported: JSONFormat and JSONxFormat). - RequiredFormat format -} - -// nonPersistentSalt is used for obtaining a salt that is -type nonPersistentSalt struct{} - -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"` - RequiredFormat format `json:"format"` -} - // Salt returns a new salt with default configuration and no storage usage, and no error. func (s *nonPersistentSalt) Salt(_ context.Context) (*salt.Salt, error) { return salt.NewNonpersistentSalt(), nil @@ -175,119 +89,6 @@ func (f *EntryFormatterWriter) FormatAndWriteResponse(ctx context.Context, w io. return f.Writer.WriteResponse(w, respEntry) } -// RequestEntry is the structure of a request audit log entry in Audit. -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 in Audit. -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"` -} - // NewTemporaryFormatter creates a formatter not backed by a persistent salt func NewTemporaryFormatter(requiredFormat, prefix string) (*EntryFormatterWriter, error) { cfg, err := NewFormatterConfig(WithFormat(requiredFormat)) diff --git a/audit/event.go b/audit/event.go index 542eba4c57..fbe9b52a2e 100644 --- a/audit/event.go +++ b/audit/event.go @@ -4,87 +4,11 @@ package audit import ( - "context" "fmt" "github.com/hashicorp/vault/internal/observability/event" - - "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 - -// 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 { - // 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 anyway. 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 anyway. 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 - - // GetHash is used to return the given data with the backend's hash, - // so that a caller can determine if a value in the audit log matches - // an expected plaintext value - GetHash(context.Context, string) (string, 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 { - // 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 -} - -// Factory is the factory function to create an audit backend. -type Factory func(context.Context, *BackendConfig, bool) (Backend, error) - // newEvent should be used to create an audit event. // subtype and format are needed for audit. // It will generate an ID if no ID is supplied. diff --git a/audit/options.go b/audit/options.go index 30cd0cbb82..569f1fe087 100644 --- a/audit/options.go +++ b/audit/options.go @@ -9,22 +9,6 @@ import ( "time" ) -// 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 -} - // getDefaultOptions returns options with their default values. func getDefaultOptions() options { return options{ diff --git a/audit/types.go b/audit/types.go new file mode 100644 index 0000000000..efbeeff239 --- /dev/null +++ b/audit/types.go @@ -0,0 +1,301 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package audit + +import ( + "context" + "io" + "time" + + "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"` + RequiredFormat format `json:"format"` +} + +// 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 +} + +// 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 // TODO: PW: Should we supply ctx in this interface + // WriteResponse writes the response entry to the writer or returns an error. + WriteResponse(io.Writer, *ResponseEntry) error +} + +// EntryFormatter should be used to format audit requests and responses. +type EntryFormatter struct { + salter Salter + 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 +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 { + // 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 + + // GetHash is used to return the given data with the backend's hash, + // so that a caller can determine if a value in the audit log matches + // an expected plaintext value + GetHash(context.Context, string) (string, 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 { + // 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 +} + +// Factory is the factory function to create an audit backend. +type Factory func(context.Context, *BackendConfig, bool) (Backend, error)