mirror of
https://github.com/hashicorp/vault.git
synced 2025-09-19 12:51:08 +02:00
This fixes #1911 but not directly; it doesn't address the cause of the panic. However, it turns out that this is the correct fix anyways, because it ensures that the value being logged is RFC3339 format, which is what the time turns into in JSON but not the normal time string value, so what we audit log (and HMAC) matches what we are returning.
332 lines
7.9 KiB
Go
332 lines
7.9 KiB
Go
package audit
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/logical"
|
|
"github.com/mitchellh/copystructure"
|
|
)
|
|
|
|
type AuditFormatWriter interface {
|
|
WriteRequest(io.Writer, *AuditRequestEntry) error
|
|
WriteResponse(io.Writer, *AuditResponseEntry) error
|
|
}
|
|
|
|
// AuditFormatter implements the Formatter interface, and allows the underlying
|
|
// marshaller to be swapped out
|
|
type AuditFormatter struct {
|
|
AuditFormatWriter
|
|
}
|
|
|
|
func (f *AuditFormatter) FormatRequest(
|
|
w io.Writer,
|
|
config FormatterConfig,
|
|
auth *logical.Auth,
|
|
req *logical.Request,
|
|
err error) error {
|
|
|
|
if w == nil {
|
|
return fmt.Errorf("writer for audit request is nil")
|
|
}
|
|
|
|
if f.AuditFormatWriter == nil {
|
|
return fmt.Errorf("no format writer specified")
|
|
}
|
|
|
|
if !config.Raw {
|
|
// Before we copy the structure we must nil out some data
|
|
// otherwise we will cause reflection to panic and die
|
|
if req.Connection != nil && req.Connection.ConnState != nil {
|
|
origReq := req
|
|
origState := req.Connection.ConnState
|
|
req.Connection.ConnState = nil
|
|
defer func() {
|
|
origReq.Connection.ConnState = origState
|
|
}()
|
|
}
|
|
|
|
// Copy the structures
|
|
cp, err := copystructure.Copy(auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
auth = cp.(*logical.Auth)
|
|
|
|
cp, err = copystructure.Copy(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req = cp.(*logical.Request)
|
|
|
|
// Hash any sensitive information
|
|
if err := Hash(config.Salt, auth); err != nil {
|
|
return err
|
|
}
|
|
if err := Hash(config.Salt, req); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If auth is nil, make an empty one
|
|
if auth == nil {
|
|
auth = new(logical.Auth)
|
|
}
|
|
var errString string
|
|
if err != nil {
|
|
errString = err.Error()
|
|
}
|
|
|
|
reqEntry := &AuditRequestEntry{
|
|
Type: "request",
|
|
Error: errString,
|
|
|
|
Auth: AuditAuth{
|
|
DisplayName: auth.DisplayName,
|
|
Policies: auth.Policies,
|
|
Metadata: auth.Metadata,
|
|
},
|
|
|
|
Request: AuditRequest{
|
|
ID: req.ID,
|
|
ClientToken: req.ClientToken,
|
|
Operation: req.Operation,
|
|
Path: req.Path,
|
|
Data: req.Data,
|
|
RemoteAddr: getRemoteAddr(req),
|
|
WrapTTL: int(req.WrapTTL / time.Second),
|
|
},
|
|
}
|
|
|
|
if !config.OmitTime {
|
|
reqEntry.Time = time.Now().UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
return f.AuditFormatWriter.WriteRequest(w, reqEntry)
|
|
}
|
|
|
|
func (f *AuditFormatter) FormatResponse(
|
|
w io.Writer,
|
|
config FormatterConfig,
|
|
auth *logical.Auth,
|
|
req *logical.Request,
|
|
resp *logical.Response,
|
|
err error) error {
|
|
|
|
if w == nil {
|
|
return fmt.Errorf("writer for audit request is nil")
|
|
}
|
|
|
|
if f.AuditFormatWriter == nil {
|
|
return fmt.Errorf("no format writer specified")
|
|
}
|
|
|
|
if !config.Raw {
|
|
// Before we copy the structure we must nil out some data
|
|
// otherwise we will cause reflection to panic and die
|
|
if req.Connection != nil && req.Connection.ConnState != nil {
|
|
origReq := req
|
|
origState := req.Connection.ConnState
|
|
req.Connection.ConnState = nil
|
|
defer func() {
|
|
origReq.Connection.ConnState = origState
|
|
}()
|
|
}
|
|
|
|
// Copy the structure
|
|
cp, err := copystructure.Copy(auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
auth = cp.(*logical.Auth)
|
|
|
|
cp, err = copystructure.Copy(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req = cp.(*logical.Request)
|
|
|
|
cp, err = copystructure.Copy(resp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp = cp.(*logical.Response)
|
|
|
|
// Hash any sensitive information
|
|
|
|
// Cache and restore accessor in the auth
|
|
var accessor, wrappedAccessor string
|
|
if !config.HMACAccessor && auth != nil && auth.Accessor != "" {
|
|
accessor = auth.Accessor
|
|
}
|
|
if err := Hash(config.Salt, auth); err != nil {
|
|
return err
|
|
}
|
|
if accessor != "" {
|
|
auth.Accessor = accessor
|
|
}
|
|
|
|
if err := Hash(config.Salt, req); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Cache and restore accessor in the response
|
|
accessor = ""
|
|
if !config.HMACAccessor && resp != nil && resp.Auth != nil && resp.Auth.Accessor != "" {
|
|
accessor = resp.Auth.Accessor
|
|
}
|
|
if !config.HMACAccessor && resp != nil && resp.WrapInfo != nil && resp.WrapInfo.WrappedAccessor != "" {
|
|
wrappedAccessor = resp.WrapInfo.WrappedAccessor
|
|
}
|
|
if err := Hash(config.Salt, resp); err != nil {
|
|
return err
|
|
}
|
|
if accessor != "" {
|
|
resp.Auth.Accessor = accessor
|
|
}
|
|
if wrappedAccessor != "" {
|
|
resp.WrapInfo.WrappedAccessor = wrappedAccessor
|
|
}
|
|
}
|
|
|
|
// If things are nil, make empty to avoid panics
|
|
if auth == nil {
|
|
auth = new(logical.Auth)
|
|
}
|
|
if resp == nil {
|
|
resp = new(logical.Response)
|
|
}
|
|
var errString string
|
|
if err != nil {
|
|
errString = err.Error()
|
|
}
|
|
|
|
var respAuth *AuditAuth
|
|
if resp.Auth != nil {
|
|
respAuth = &AuditAuth{
|
|
ClientToken: resp.Auth.ClientToken,
|
|
Accessor: resp.Auth.Accessor,
|
|
DisplayName: resp.Auth.DisplayName,
|
|
Policies: resp.Auth.Policies,
|
|
Metadata: resp.Auth.Metadata,
|
|
}
|
|
}
|
|
|
|
var respSecret *AuditSecret
|
|
if resp.Secret != nil {
|
|
respSecret = &AuditSecret{
|
|
LeaseID: resp.Secret.LeaseID,
|
|
}
|
|
}
|
|
|
|
var respWrapInfo *AuditWrapInfo
|
|
if resp.WrapInfo != nil {
|
|
respWrapInfo = &AuditWrapInfo{
|
|
TTL: int(resp.WrapInfo.TTL / time.Second),
|
|
Token: resp.WrapInfo.Token,
|
|
CreationTime: resp.WrapInfo.CreationTime.Format(time.RFC3339Nano),
|
|
WrappedAccessor: resp.WrapInfo.WrappedAccessor,
|
|
}
|
|
}
|
|
|
|
respEntry := &AuditResponseEntry{
|
|
Type: "response",
|
|
Error: errString,
|
|
|
|
Auth: AuditAuth{
|
|
DisplayName: auth.DisplayName,
|
|
Policies: auth.Policies,
|
|
Metadata: auth.Metadata,
|
|
},
|
|
|
|
Request: AuditRequest{
|
|
ID: req.ID,
|
|
ClientToken: req.ClientToken,
|
|
Operation: req.Operation,
|
|
Path: req.Path,
|
|
Data: req.Data,
|
|
RemoteAddr: getRemoteAddr(req),
|
|
WrapTTL: int(req.WrapTTL / time.Second),
|
|
},
|
|
|
|
Response: AuditResponse{
|
|
Auth: respAuth,
|
|
Secret: respSecret,
|
|
Data: resp.Data,
|
|
Redirect: resp.Redirect,
|
|
WrapInfo: respWrapInfo,
|
|
},
|
|
}
|
|
|
|
if !config.OmitTime {
|
|
respEntry.Time = time.Now().UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
return f.AuditFormatWriter.WriteResponse(w, respEntry)
|
|
}
|
|
|
|
// AuditRequest is the structure of a request audit log entry in Audit.
|
|
type AuditRequestEntry struct {
|
|
Time string `json:"time,omitempty"`
|
|
Type string `json:"type"`
|
|
Auth AuditAuth `json:"auth"`
|
|
Request AuditRequest `json:"request"`
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// AuditResponseEntry is the structure of a response audit log entry in Audit.
|
|
type AuditResponseEntry struct {
|
|
Time string `json:"time,omitempty"`
|
|
Type string `json:"type"`
|
|
Error string `json:"error"`
|
|
Auth AuditAuth `json:"auth"`
|
|
Request AuditRequest `json:"request"`
|
|
Response AuditResponse `json:"response"`
|
|
}
|
|
|
|
type AuditRequest struct {
|
|
ID string `json:"id"`
|
|
Operation logical.Operation `json:"operation"`
|
|
ClientToken string `json:"client_token"`
|
|
Path string `json:"path"`
|
|
Data map[string]interface{} `json:"data"`
|
|
RemoteAddr string `json:"remote_address"`
|
|
WrapTTL int `json:"wrap_ttl"`
|
|
}
|
|
|
|
type AuditResponse struct {
|
|
Auth *AuditAuth `json:"auth,omitempty"`
|
|
Secret *AuditSecret `json:"secret,emitempty"`
|
|
Data map[string]interface{} `json:"data"`
|
|
Redirect string `json:"redirect"`
|
|
WrapInfo *AuditWrapInfo `json:"wrap_info,omitempty"`
|
|
}
|
|
|
|
type AuditAuth struct {
|
|
ClientToken string `json:"client_token,omitempty"`
|
|
Accessor string `json:"accessor,omitempty"`
|
|
DisplayName string `json:"display_name"`
|
|
Policies []string `json:"policies"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
}
|
|
|
|
type AuditSecret struct {
|
|
LeaseID string `json:"lease_id"`
|
|
}
|
|
|
|
type AuditWrapInfo struct {
|
|
TTL int `json:"ttl"`
|
|
Token string `json:"token"`
|
|
CreationTime string `json:"creation_time"`
|
|
WrappedAccessor string `json:"wrapped_accessor,omitempty"`
|
|
}
|
|
|
|
// getRemoteAddr safely gets the remote address avoiding a nil pointer
|
|
func getRemoteAddr(req *logical.Request) string {
|
|
if req != nil && req.Connection != nil {
|
|
return req.Connection.RemoteAddr
|
|
}
|
|
return ""
|
|
}
|