mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-29 17:52:32 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			617 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			617 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) HashiCorp, Inc.
 | |
| // SPDX-License-Identifier: BUSL-1.1
 | |
| 
 | |
| package audit
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"runtime/debug"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/go-jose/go-jose/v3/jwt"
 | |
| 	"github.com/hashicorp/eventlogger"
 | |
| 	"github.com/hashicorp/go-hclog"
 | |
| 	nshelper "github.com/hashicorp/vault/helper/namespace"
 | |
| 	"github.com/hashicorp/vault/sdk/helper/jsonutil"
 | |
| 	"github.com/hashicorp/vault/sdk/helper/salt"
 | |
| 	"github.com/hashicorp/vault/sdk/logical"
 | |
| 	"github.com/jefferai/jsonx"
 | |
| 	"github.com/mitchellh/copystructure"
 | |
| )
 | |
| 
 | |
| var _ eventlogger.Node = (*entryFormatter)(nil)
 | |
| 
 | |
| // timeProvider offers a way to supply a pre-configured time.
 | |
| type timeProvider interface {
 | |
| 	// formatTime provides the pre-configured time in a particular format.
 | |
| 	formattedTime() string
 | |
| }
 | |
| 
 | |
| // nonPersistentSalt is used for obtaining a salt that is not persisted.
 | |
| type nonPersistentSalt struct{}
 | |
| 
 | |
| // entryFormatter should be used to format audit requests and responses.
 | |
| // NOTE: Use newEntryFormatter to initialize the entryFormatter struct.
 | |
| type entryFormatter struct {
 | |
| 	config formatterConfig
 | |
| 	salter Salter
 | |
| 	logger hclog.Logger
 | |
| 	name   string
 | |
| }
 | |
| 
 | |
| // newEntryFormatter should be used to create an entryFormatter.
 | |
| func newEntryFormatter(name string, config formatterConfig, salter Salter, logger hclog.Logger) (*entryFormatter, error) {
 | |
| 	name = strings.TrimSpace(name)
 | |
| 	if name == "" {
 | |
| 		return nil, fmt.Errorf("name is required: %w", ErrInvalidParameter)
 | |
| 	}
 | |
| 
 | |
| 	if salter == nil {
 | |
| 		return nil, fmt.Errorf("cannot create a new audit formatter with nil salter: %w", ErrInvalidParameter)
 | |
| 	}
 | |
| 
 | |
| 	if logger == nil || reflect.ValueOf(logger).IsNil() {
 | |
| 		return nil, fmt.Errorf("cannot create a new audit formatter with nil logger: %w", ErrInvalidParameter)
 | |
| 	}
 | |
| 
 | |
| 	return &entryFormatter{
 | |
| 		config: config,
 | |
| 		salter: salter,
 | |
| 		logger: logger,
 | |
| 		name:   name,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // Reopen is a no-op for the formatter node.
 | |
| func (*entryFormatter) Reopen() error {
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Type describes the type of this node (formatter).
 | |
| func (*entryFormatter) Type() eventlogger.NodeType {
 | |
| 	return eventlogger.NodeTypeFormatter
 | |
| }
 | |
| 
 | |
| // Process will attempt to parse the incoming event data into a corresponding
 | |
| // audit request/response which is serialized to JSON/JSONx and stored within the event.
 | |
| func (f *entryFormatter) Process(ctx context.Context, e *eventlogger.Event) (_ *eventlogger.Event, retErr error) {
 | |
| 	// 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("event is nil: %w", ErrInvalidParameter)
 | |
| 	}
 | |
| 
 | |
| 	a, ok := e.Payload.(*Event)
 | |
| 	if !ok {
 | |
| 		return nil, fmt.Errorf("cannot parse event payload: %w", ErrInvalidParameter)
 | |
| 	}
 | |
| 
 | |
| 	if a.Data == nil {
 | |
| 		return nil, fmt.Errorf("cannot audit a '%s' event with no data: %w", a.Subtype, ErrInvalidParameter)
 | |
| 	}
 | |
| 
 | |
| 	// Handle panics
 | |
| 	defer func() {
 | |
| 		r := recover()
 | |
| 		if r == nil {
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		path := "unknown"
 | |
| 		if a.Data.Request != nil {
 | |
| 			path = a.Data.Request.Path
 | |
| 		}
 | |
| 
 | |
| 		f.logger.Error("panic during logging",
 | |
| 			"request_path", path,
 | |
| 			"audit_device_path", f.name,
 | |
| 			"error", r,
 | |
| 			"stacktrace", string(debug.Stack()))
 | |
| 
 | |
| 		// Ensure that we add this error onto any pre-existing error that was being returned.
 | |
| 		retErr = errors.Join(retErr, fmt.Errorf("panic generating audit log: %q", f.name))
 | |
| 	}()
 | |
| 
 | |
| 	// Using 'any' to make exclusion easier, the JSON encoder doesn't care about types.
 | |
| 	var entry any
 | |
| 	var err error
 | |
| 	entry, err = f.createEntry(ctx, a)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// If this pipeline has been configured with (Enterprise-only) exclusions then
 | |
| 	// attempt to exclude the fields from the audit entry.
 | |
| 	if f.shouldExclude() {
 | |
| 		m, err := f.excludeFields(entry)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("unable to exclude %s audit data from %q: %w", a.Subtype, f.name, err)
 | |
| 		}
 | |
| 
 | |
| 		entry = m
 | |
| 	}
 | |
| 
 | |
| 	result, err := jsonutil.EncodeJSON(entry)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to format %s: %w", a.Subtype, err)
 | |
| 	}
 | |
| 
 | |
| 	if f.config.requiredFormat == jsonxFormat {
 | |
| 		var err error
 | |
| 		result, err = jsonx.EncodeJSONBytes(result)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("unable to encode JSONx using JSON data: %w", err)
 | |
| 		}
 | |
| 		if result == nil {
 | |
| 			return nil, fmt.Errorf("encoded JSONx was nil: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// This makes a bit of a mess of the 'format' since both JSON and XML (JSONx)
 | |
| 	// don't support a prefix just sitting there.
 | |
| 	// However, this would be a breaking change to how Vault currently works to
 | |
| 	// include the prefix as part of the JSON object or XML document.
 | |
| 	if f.config.prefix != "" {
 | |
| 		result = append([]byte(f.config.prefix), result...)
 | |
| 	}
 | |
| 
 | |
| 	// Create a new event, so we can store our formatted data without conflict.
 | |
| 	e2 := &eventlogger.Event{
 | |
| 		Type:      e.Type,
 | |
| 		CreatedAt: e.CreatedAt,
 | |
| 		Formatted: make(map[string][]byte), // we are about to set this ourselves.
 | |
| 		Payload:   a,
 | |
| 	}
 | |
| 
 | |
| 	e2.FormattedAs(f.config.requiredFormat.String(), result)
 | |
| 
 | |
| 	return e2, nil
 | |
| }
 | |
| 
 | |
| // remoteAddr safely gets the remote address avoiding a nil pointer.
 | |
| func remoteAddr(req *logical.Request) string {
 | |
| 	if req != nil && req.Connection != nil {
 | |
| 		return req.Connection.RemoteAddr
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // remotePort safely gets the remote port avoiding a nil pointer.
 | |
| func remotePort(req *logical.Request) int {
 | |
| 	if req != nil && req.Connection != nil {
 | |
| 		return req.Connection.RemotePort
 | |
| 	}
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| // clientCertSerialNumber attempts the retrieve the serial number of the peer
 | |
| // certificate from the specified tls.ConnectionState.
 | |
| func clientCertSerialNumber(req *logical.Request) string {
 | |
| 	if req == nil || req.Connection == nil {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	connState := req.Connection.ConnState
 | |
| 
 | |
| 	if connState == nil || len(connState.VerifiedChains) == 0 || len(connState.VerifiedChains[0]) == 0 {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	return connState.VerifiedChains[0][0].SerialNumber.String()
 | |
| }
 | |
| 
 | |
| // parseVaultTokenFromJWT returns a string iff the token was a JWT, and we could
 | |
| // extract the original token ID from inside
 | |
| func parseVaultTokenFromJWT(token string) *string {
 | |
| 	if strings.Count(token, ".") != 2 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	parsedJWT, err := jwt.ParseSigned(token)
 | |
| 	if err != nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var claims jwt.Claims
 | |
| 	if err = parsedJWT.UnsafeClaimsWithoutVerification(&claims); err != nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	return &claims.ID
 | |
| }
 | |
| 
 | |
| // newTemporaryEntryFormatter creates a cloned entryFormatter instance with a non-persistent Salter.
 | |
| func newTemporaryEntryFormatter(n *entryFormatter) *entryFormatter {
 | |
| 	return &entryFormatter{
 | |
| 		salter: &nonPersistentSalt{},
 | |
| 		config: n.config,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // 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
 | |
| }
 | |
| 
 | |
| // clone can be used to deep clone the specified type.
 | |
| func clone[V any](s V) (V, error) {
 | |
| 	s2, err := copystructure.Copy(s)
 | |
| 
 | |
| 	return s2.(V), err
 | |
| }
 | |
| 
 | |
| // newAuth takes a logical.Auth and the number of remaining client token uses
 | |
| // (which should be supplied from the logical.Request's client token), and creates
 | |
| // an audit auth.
 | |
| // tokenRemainingUses should be the client token remaining uses to include in auth.
 | |
| // This usually can be found in logical.Request.ClientTokenRemainingUses.
 | |
| // NOTE: supplying a nil value for auth will result in a nil return value and
 | |
| // (nil) error. The caller should check the return value before attempting to use it.
 | |
| // ignore-nil-nil-function-check.
 | |
| func newAuth(input *logical.Auth, tokenRemainingUses int) (*auth, error) {
 | |
| 	if input == nil {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	extNSPolicies, err := clone(input.ExternalNamespacePolicies)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical auth: external namespace policies: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	identityPolicies, err := clone(input.IdentityPolicies)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical auth: identity policies: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	metadata, err := clone(input.Metadata)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical auth: metadata: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	policies, err := clone(input.Policies)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical auth: policies: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	var polRes *policyResults
 | |
| 	if input.PolicyResults != nil {
 | |
| 		polRes = &policyResults{
 | |
| 			Allowed:          input.PolicyResults.Allowed,
 | |
| 			GrantingPolicies: make([]policyInfo, len(input.PolicyResults.GrantingPolicies)),
 | |
| 		}
 | |
| 
 | |
| 		for _, p := range input.PolicyResults.GrantingPolicies {
 | |
| 			polRes.GrantingPolicies = append(polRes.GrantingPolicies, policyInfo{
 | |
| 				Name:          p.Name,
 | |
| 				NamespaceId:   p.NamespaceId,
 | |
| 				NamespacePath: p.NamespacePath,
 | |
| 				Type:          p.Type,
 | |
| 			})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	tokenPolicies, err := clone(input.TokenPolicies)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical auth: token policies: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	var tokenIssueTime string
 | |
| 	if !input.IssueTime.IsZero() {
 | |
| 		tokenIssueTime = input.IssueTime.Format(time.RFC3339)
 | |
| 	}
 | |
| 
 | |
| 	return &auth{
 | |
| 		Accessor:                  input.Accessor,
 | |
| 		ClientToken:               input.ClientToken,
 | |
| 		DisplayName:               input.DisplayName,
 | |
| 		EntityCreated:             input.EntityCreated,
 | |
| 		EntityID:                  input.EntityID,
 | |
| 		ExternalNamespacePolicies: extNSPolicies,
 | |
| 		IdentityPolicies:          identityPolicies,
 | |
| 		Metadata:                  metadata,
 | |
| 		NoDefaultPolicy:           input.NoDefaultPolicy,
 | |
| 		NumUses:                   input.NumUses,
 | |
| 		Policies:                  policies,
 | |
| 		PolicyResults:             polRes,
 | |
| 		RemainingUses:             tokenRemainingUses,
 | |
| 		TokenPolicies:             tokenPolicies,
 | |
| 		TokenIssueTime:            tokenIssueTime,
 | |
| 		TokenTTL:                  int64(input.TTL.Seconds()),
 | |
| 		TokenType:                 input.TokenType.String(),
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // newRequest takes a logical.Request and namespace.Namespace, transforms and
 | |
| // aggregates them into an audit request.
 | |
| func newRequest(req *logical.Request, ns *nshelper.Namespace) (*request, error) {
 | |
| 	if req == nil {
 | |
| 		return nil, fmt.Errorf("request cannot be nil")
 | |
| 	}
 | |
| 
 | |
| 	remoteAddr := remoteAddr(req)
 | |
| 	remotePort := remotePort(req)
 | |
| 	clientCertSerial := clientCertSerialNumber(req)
 | |
| 
 | |
| 	data, err := clone(req.Data)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical request: data: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	headers, err := clone(req.Headers)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical request: headers: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	var reqURI string
 | |
| 	if req.HTTPRequest != nil && req.HTTPRequest.RequestURI != req.Path {
 | |
| 		reqURI = req.HTTPRequest.RequestURI
 | |
| 	}
 | |
| 	var wrapTTL int
 | |
| 	if req.WrapInfo != nil {
 | |
| 		wrapTTL = int(req.WrapInfo.TTL / time.Second)
 | |
| 	}
 | |
| 
 | |
| 	return &request{
 | |
| 		ClientCertificateSerialNumber: clientCertSerial,
 | |
| 		ClientID:                      req.ClientID,
 | |
| 		ClientToken:                   req.ClientToken,
 | |
| 		ClientTokenAccessor:           req.ClientTokenAccessor,
 | |
| 		Data:                          data,
 | |
| 		Headers:                       headers,
 | |
| 		ID:                            req.ID,
 | |
| 		MountAccessor:                 req.MountAccessor,
 | |
| 		MountClass:                    req.MountClass(),
 | |
| 		MountIsExternalPlugin:         req.MountIsExternalPlugin(),
 | |
| 		MountPoint:                    req.MountPoint,
 | |
| 		MountRunningSha256:            req.MountRunningSha256(),
 | |
| 		MountRunningVersion:           req.MountRunningVersion(),
 | |
| 		MountType:                     req.MountType,
 | |
| 		Namespace: &namespace{
 | |
| 			ID:   ns.ID,
 | |
| 			Path: ns.Path,
 | |
| 		},
 | |
| 		Operation:          req.Operation,
 | |
| 		Path:               req.Path,
 | |
| 		PolicyOverride:     req.PolicyOverride,
 | |
| 		RemoteAddr:         remoteAddr,
 | |
| 		RemotePort:         remotePort,
 | |
| 		ReplicationCluster: req.ReplicationCluster,
 | |
| 		RequestURI:         reqURI,
 | |
| 		WrapTTL:            wrapTTL,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // newResponse takes a logical.Response and logical.Request, transforms and
 | |
| // aggregates them into an audit response.
 | |
| // isElisionRequired is used to indicate that response 'Data' should be elided.
 | |
| // NOTE: supplying a nil value for response will result in a nil return value and
 | |
| // (nil) error. The caller should check the return value before attempting to use it.
 | |
| // ignore-nil-nil-function-check.
 | |
| func newResponse(resp *logical.Response, req *logical.Request, isElisionRequired bool) (*response, error) {
 | |
| 	if resp == nil {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	if req == nil {
 | |
| 		// Request should never be nil, even for a response.
 | |
| 		return nil, fmt.Errorf("request cannot be nil")
 | |
| 	}
 | |
| 
 | |
| 	auth, err := newAuth(resp.Auth, req.ClientTokenRemainingUses)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to convert logical auth response: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	var data map[string]any
 | |
| 	if resp.Data != nil {
 | |
| 		data = make(map[string]any, len(resp.Data))
 | |
| 
 | |
| 		if isElisionRequired {
 | |
| 			// Performs the actual elision (ideally for list operations) of response data,
 | |
| 			// once surrounding code has determined it should apply to a particular request.
 | |
| 			// If the value for a key should not be elided, then it will be cloned.
 | |
| 			for k, v := range resp.Data {
 | |
| 				isCloneRequired := true
 | |
| 				switch k {
 | |
| 				case "keys":
 | |
| 					if vSlice, ok := v.([]string); ok {
 | |
| 						data[k] = len(vSlice)
 | |
| 						isCloneRequired = false
 | |
| 					}
 | |
| 				case "key_info":
 | |
| 					if vMap, ok := v.(map[string]any); ok {
 | |
| 						data[k] = len(vMap)
 | |
| 						isCloneRequired = false
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				// Clone values if they weren't legitimate keys or key_info.
 | |
| 				if isCloneRequired {
 | |
| 					v2, err := clone(v)
 | |
| 					if err != nil {
 | |
| 						return nil, fmt.Errorf("unable to clone response data while eliding: %w", err)
 | |
| 					}
 | |
| 					data[k] = v2
 | |
| 				}
 | |
| 			}
 | |
| 		} else {
 | |
| 			// Deep clone all values, no shortcuts here.
 | |
| 			data, err = clone(resp.Data)
 | |
| 			if err != nil {
 | |
| 				return nil, fmt.Errorf("unable to clone response data: %w", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	headers, err := clone(resp.Headers)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical response: headers: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	var s *secret
 | |
| 	if resp.Secret != nil {
 | |
| 		s = &secret{LeaseID: resp.Secret.LeaseID}
 | |
| 	}
 | |
| 
 | |
| 	var wrapInfo *responseWrapInfo
 | |
| 	if resp.WrapInfo != nil {
 | |
| 		token := resp.WrapInfo.Token
 | |
| 		if jwtToken := parseVaultTokenFromJWT(token); jwtToken != nil {
 | |
| 			token = *jwtToken
 | |
| 		}
 | |
| 
 | |
| 		ttl := int(resp.WrapInfo.TTL / time.Second)
 | |
| 		wrapInfo = &responseWrapInfo{
 | |
| 			TTL:             ttl,
 | |
| 			Token:           token,
 | |
| 			Accessor:        resp.WrapInfo.Accessor,
 | |
| 			CreationTime:    resp.WrapInfo.CreationTime.UTC().Format(time.RFC3339Nano),
 | |
| 			CreationPath:    resp.WrapInfo.CreationPath,
 | |
| 			WrappedAccessor: resp.WrapInfo.WrappedAccessor,
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	warnings, err := clone(resp.Warnings)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to clone logical response: warnings: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	return &response{
 | |
| 		Auth:                  auth,
 | |
| 		Data:                  data,
 | |
| 		Headers:               headers,
 | |
| 		MountAccessor:         req.MountAccessor,
 | |
| 		MountClass:            req.MountClass(),
 | |
| 		MountIsExternalPlugin: req.MountIsExternalPlugin(),
 | |
| 		MountPoint:            req.MountPoint,
 | |
| 		MountRunningSha256:    req.MountRunningSha256(),
 | |
| 		MountRunningVersion:   req.MountRunningVersion(),
 | |
| 		MountType:             req.MountType,
 | |
| 		Redirect:              resp.Redirect,
 | |
| 		Secret:                s,
 | |
| 		WrapInfo:              wrapInfo,
 | |
| 		Warnings:              warnings,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| // createEntry takes the AuditEvent and builds an audit entry.
 | |
| // The entry will be HMAC'd and elided where required.
 | |
| func (f *entryFormatter) createEntry(ctx context.Context, a *Event) (*entry, error) {
 | |
| 	select {
 | |
| 	case <-ctx.Done():
 | |
| 		return nil, ctx.Err()
 | |
| 	default:
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	data := a.Data
 | |
| 
 | |
| 	if data.Request == nil {
 | |
| 		// Request should never be nil, even for a response.
 | |
| 		return nil, fmt.Errorf("unable to parse request from '%s' audit event: request cannot be nil", a.Subtype)
 | |
| 	}
 | |
| 
 | |
| 	ns, err := nshelper.FromContext(ctx)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("unable to retrieve namespace from context: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	auth, err := newAuth(data.Auth, data.Request.ClientTokenRemainingUses)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("cannot convert auth: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	req, err := newRequest(data.Request, ns)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("cannot convert request: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	var resp *response
 | |
| 	if a.Subtype == ResponseType {
 | |
| 		shouldElide := f.config.elideListResponses && req.Operation == logical.ListOperation
 | |
| 		resp, err = newResponse(data.Response, data.Request, shouldElide)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("cannot convert response: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	var outerErr string
 | |
| 	if data.OuterErr != nil {
 | |
| 		outerErr = data.OuterErr.Error()
 | |
| 	}
 | |
| 
 | |
| 	entryType := data.Type
 | |
| 	if entryType == "" {
 | |
| 		entryType = a.Subtype.String()
 | |
| 	}
 | |
| 
 | |
| 	entry := &entry{
 | |
| 		Auth:          auth,
 | |
| 		Error:         outerErr,
 | |
| 		Forwarded:     false,
 | |
| 		ForwardedFrom: data.Request.ForwardedFrom,
 | |
| 		Request:       req,
 | |
| 		Response:      resp,
 | |
| 		Type:          entryType,
 | |
| 	}
 | |
| 
 | |
| 	if !f.config.omitTime {
 | |
| 		// Use the time provider to supply the time for this entry.
 | |
| 		entry.Time = a.timeProvider().formattedTime()
 | |
| 	}
 | |
| 
 | |
| 	// If the request is present in the input data, apply header configuration
 | |
| 	// regardless. We shouldn't be in a situation where the header formatter isn't
 | |
| 	// present as it's required.
 | |
| 	if entry.Request != nil {
 | |
| 		// 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
 | |
| 		entry.Request.Headers, err = f.config.headerFormatter.ApplyConfig(ctx, entry.Request.Headers, f.salter)
 | |
| 		if err != nil {
 | |
| 			return nil, fmt.Errorf("unable to transform headers for auditing: %w", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// If the request contains a Server-Side Consistency Token (SSCT), and we
 | |
| 	// have an auth response, overwrite the existing client token with the SSCT,
 | |
| 	// so that the SSCT appears in the audit log for this entry.
 | |
| 	if data.Request != nil && data.Request.InboundSSCToken != "" && entry.Auth != nil {
 | |
| 		entry.Auth.ClientToken = data.Request.InboundSSCToken
 | |
| 	}
 | |
| 
 | |
| 	// Hash the entry if we aren't expecting raw output.
 | |
| 	if !f.config.raw {
 | |
| 		// Requests and responses have auth and request.
 | |
| 		err = hashAuth(ctx, f.salter, entry.Auth, f.config.hmacAccessor)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		err = hashRequest(ctx, f.salter, entry.Request, f.config.hmacAccessor, data.NonHMACReqDataKeys)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		if a.Subtype == ResponseType {
 | |
| 			if err = hashResponse(ctx, f.salter, entry.Response, f.config.hmacAccessor, data.NonHMACRespDataKeys); err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return entry, nil
 | |
| }
 | 
