mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	Audit: some refactoring out of types.go (#25511)
* some refactoring out of types.go * tests for metrics tag, parallelize other tests
This commit is contained in:
		| @@ -17,6 +17,13 @@ import ( | ||||
|  | ||||
| var _ eventlogger.Node = (*EntryFilter)(nil) | ||||
|  | ||||
| // EntryFilter should be used to filter audit requests and responses which should | ||||
| // make it to a sink. | ||||
| type EntryFilter struct { | ||||
| 	// the evaluator for the bexpr expression that should be applied by the node. | ||||
| 	evaluator *bexpr.Evaluator | ||||
| } | ||||
|  | ||||
| // NewEntryFilter should be used to create an EntryFilter node. | ||||
| // The filter supplied should be in bexpr format and reference fields from logical.LogInputBexpr. | ||||
| func NewEntryFilter(filter string) (*EntryFilter, error) { | ||||
|   | ||||
| @@ -26,6 +26,14 @@ var ( | ||||
| 	_ eventlogger.Node = (*EntryFormatter)(nil) | ||||
| ) | ||||
|  | ||||
| // EntryFormatter should be used to format audit requests and responses. | ||||
| type EntryFormatter struct { | ||||
| 	salter          Salter | ||||
| 	headerFormatter HeaderFormatter | ||||
| 	config          FormatterConfig | ||||
| 	prefix          string | ||||
| } | ||||
|  | ||||
| // NewEntryFormatter should be used to create an EntryFormatter. | ||||
| // Accepted options: WithHeaderFormatter, WithPrefix. | ||||
| func NewEntryFormatter(config FormatterConfig, salter Salter, opt ...Option) (*EntryFormatter, error) { | ||||
|   | ||||
| @@ -59,6 +59,8 @@ const testFormatJSONReqBasicStrFmt = ` | ||||
|  | ||||
| // TestNewEntryFormatter ensures we can create new EntryFormatter structs. | ||||
| func TestNewEntryFormatter(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		UseStaticSalt        bool | ||||
| 		Options              []Option // Only supports WithPrefix | ||||
| @@ -154,6 +156,8 @@ func TestNewEntryFormatter(t *testing.T) { | ||||
|  | ||||
| // TestEntryFormatter_Reopen ensures that we do not get an error when calling Reopen. | ||||
| func TestEntryFormatter_Reopen(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	ss := newStaticSalt(t) | ||||
| 	cfg, err := NewFormatterConfig() | ||||
| 	require.NoError(t, err) | ||||
| @@ -166,6 +170,8 @@ func TestEntryFormatter_Reopen(t *testing.T) { | ||||
|  | ||||
| // TestEntryFormatter_Type ensures that the node is a 'formatter' type. | ||||
| func TestEntryFormatter_Type(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	ss := newStaticSalt(t) | ||||
| 	cfg, err := NewFormatterConfig() | ||||
| 	require.NoError(t, err) | ||||
| @@ -179,6 +185,8 @@ func TestEntryFormatter_Type(t *testing.T) { | ||||
| // TestEntryFormatter_Process attempts to run the Process method to convert the | ||||
| // logical.LogInput within an audit event to JSON and JSONx (RequestEntry or ResponseEntry). | ||||
| func TestEntryFormatter_Process(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		IsErrorExpected      bool | ||||
| 		ExpectedErrorMessage string | ||||
| @@ -409,6 +417,8 @@ func BenchmarkAuditFileSink_Process(b *testing.B) { | ||||
| // TestEntryFormatter_FormatRequest exercises EntryFormatter.FormatRequest with | ||||
| // varying inputs. | ||||
| func TestEntryFormatter_FormatRequest(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Input                *logical.LogInput | ||||
| 		IsErrorExpected      bool | ||||
| @@ -476,6 +486,8 @@ func TestEntryFormatter_FormatRequest(t *testing.T) { | ||||
| // TestEntryFormatter_FormatResponse exercises EntryFormatter.FormatResponse with | ||||
| // varying inputs. | ||||
| func TestEntryFormatter_FormatResponse(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Input                *logical.LogInput | ||||
| 		IsErrorExpected      bool | ||||
| @@ -543,6 +555,8 @@ func TestEntryFormatter_FormatResponse(t *testing.T) { | ||||
| // TestEntryFormatter_Process_JSON ensures that the JSON output we get matches what | ||||
| // we expect for the specified LogInput. | ||||
| func TestEntryFormatter_Process_JSON(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	ss := newStaticSalt(t) | ||||
|  | ||||
| 	expectedResultStr := fmt.Sprintf(testFormatJSONReqBasicStrFmt, ss.salt.GetIdentifiedHMAC("foo")) | ||||
| @@ -683,6 +697,8 @@ func TestEntryFormatter_Process_JSON(t *testing.T) { | ||||
| // TestEntryFormatter_Process_JSONx ensures that the JSONx output we get matches what | ||||
| // we expect for the specified LogInput. | ||||
| func TestEntryFormatter_Process_JSONx(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	s, err := salt.NewSalt(context.Background(), nil, nil) | ||||
| 	require.NoError(t, err) | ||||
| 	tempStaticSalt := &staticSalt{salt: s} | ||||
| @@ -826,11 +842,7 @@ func TestEntryFormatter_Process_JSONx(t *testing.T) { | ||||
| // TestEntryFormatter_FormatResponse_ElideListResponses ensures that we correctly | ||||
| // elide data in responses to LIST operations. | ||||
| func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) { | ||||
| 	type test struct { | ||||
| 		name         string | ||||
| 		inputData    map[string]any | ||||
| 		expectedData map[string]any | ||||
| 	} | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		inputData    map[string]any | ||||
| @@ -957,6 +969,8 @@ func TestEntryFormatter_FormatResponse_ElideListResponses(t *testing.T) { | ||||
| // TestEntryFormatter_Process_NoMutation tests that the event returned by an | ||||
| // EntryFormatter.Process method is not the same as the one that it accepted. | ||||
| func TestEntryFormatter_Process_NoMutation(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	// Create the formatter node. | ||||
| 	cfg, err := NewFormatterConfig() | ||||
| 	require.NoError(t, err) | ||||
|   | ||||
| @@ -5,10 +5,42 @@ package audit | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/hashicorp/vault/internal/observability/event" | ||||
| 	"github.com/hashicorp/vault/sdk/logical" | ||||
| ) | ||||
|  | ||||
| // version defines the version of audit events. | ||||
| const version = "v0.1" | ||||
|  | ||||
| // Audit subtypes. | ||||
| const ( | ||||
| 	RequestType  subtype = "AuditRequest" | ||||
| 	ResponseType subtype = "AuditResponse" | ||||
| ) | ||||
|  | ||||
| // Audit formats. | ||||
| const ( | ||||
| 	JSONFormat  format = "json" | ||||
| 	JSONxFormat format = "jsonx" | ||||
| ) | ||||
|  | ||||
| // 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"` | ||||
| } | ||||
|  | ||||
| // format defines types of format audit events support. | ||||
| type format string | ||||
|  | ||||
| // subtype defines the type of audit event. | ||||
| type subtype string | ||||
|  | ||||
| // NewEvent should be used to create an audit event. The subtype field is needed | ||||
| // for audit events. It will generate an ID if no ID is supplied. Supported | ||||
| // options: WithID, WithNow. | ||||
| @@ -99,13 +131,14 @@ func (f format) String() string { | ||||
| } | ||||
|  | ||||
| // MetricTag returns a tag corresponding to this subtype to include in metrics. | ||||
| func (st subtype) MetricTag() string { | ||||
| 	switch st { | ||||
| // If a tag cannot be found the value is returned 'as-is' in string format. | ||||
| func (t subtype) MetricTag() string { | ||||
| 	switch t { | ||||
| 	case RequestType: | ||||
| 		return "log_request" | ||||
| 	case ResponseType: | ||||
| 		return "log_response" | ||||
| 	} | ||||
|  | ||||
| 	return "" | ||||
| 	return string(t) | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,8 @@ import ( | ||||
|  | ||||
| // TestAuditEvent_new exercises the newEvent func to create audit events. | ||||
| func TestAuditEvent_new(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Options              []Option | ||||
| 		Subtype              subtype | ||||
| @@ -107,6 +109,8 @@ func TestAuditEvent_new(t *testing.T) { | ||||
|  | ||||
| // TestAuditEvent_Validate exercises the validation for an audit event. | ||||
| func TestAuditEvent_Validate(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                *AuditEvent | ||||
| 		IsErrorExpected      bool | ||||
| @@ -198,6 +202,8 @@ func TestAuditEvent_Validate(t *testing.T) { | ||||
|  | ||||
| // TestAuditEvent_Validate_Subtype exercises the validation for an audit event's subtype. | ||||
| func TestAuditEvent_Validate_Subtype(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                string | ||||
| 		IsErrorExpected      bool | ||||
| @@ -243,6 +249,8 @@ func TestAuditEvent_Validate_Subtype(t *testing.T) { | ||||
|  | ||||
| // TestAuditEvent_Validate_Format exercises the validation for an audit event's format. | ||||
| func TestAuditEvent_Validate_Format(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                string | ||||
| 		IsErrorExpected      bool | ||||
| @@ -285,3 +293,42 @@ func TestAuditEvent_Validate_Format(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TestAuditEvent_Subtype_MetricTag is used to ensure that we get the string value | ||||
| // we expect for a subtype when we want to use it as a metrics tag. | ||||
| // In some strange scenario where the subtype was never validated, it is technically | ||||
| // possible to get a value that isn't related to request/response, but this shouldn't | ||||
| // really be happening, so we will return it as is. | ||||
| func TestAuditEvent_Subtype_MetricTag(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		input          string | ||||
| 		expectedOutput string | ||||
| 	}{ | ||||
| 		"request": { | ||||
| 			input:          "AuditRequest", | ||||
| 			expectedOutput: "log_request", | ||||
| 		}, | ||||
| 		"response": { | ||||
| 			input:          "AuditResponse", | ||||
| 			expectedOutput: "log_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) | ||||
| 			tag := st.MetricTag() | ||||
| 			require.Equal(t, tc.expectedOutput, tag) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -40,6 +40,8 @@ func TestProcessManual_NilData(t *testing.T) { | ||||
| // TestProcessManual_BadIDs tests ProcessManual when different bad values are | ||||
| // supplied for the ID parameter. | ||||
| func TestProcessManual_BadIDs(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		IDs                  []eventlogger.NodeID | ||||
| 		ExpectedErrorMessage string | ||||
|   | ||||
| @@ -10,6 +10,23 @@ 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 | ||||
| 	withHeaderFormatter HeaderFormatter | ||||
| } | ||||
|  | ||||
| // getDefaultOptions returns options with their default values. | ||||
| func getDefaultOptions() options { | ||||
| 	return options{ | ||||
|   | ||||
| @@ -13,6 +13,8 @@ import ( | ||||
|  | ||||
| // TestOptions_WithFormat exercises WithFormat Option to ensure it performs as expected. | ||||
| func TestOptions_WithFormat(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                string | ||||
| 		IsErrorExpected      bool | ||||
| @@ -68,6 +70,8 @@ func TestOptions_WithFormat(t *testing.T) { | ||||
|  | ||||
| // TestOptions_WithSubtype exercises WithSubtype Option to ensure it performs as expected. | ||||
| func TestOptions_WithSubtype(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                string | ||||
| 		IsErrorExpected      bool | ||||
| @@ -113,6 +117,8 @@ func TestOptions_WithSubtype(t *testing.T) { | ||||
|  | ||||
| // TestOptions_WithNow exercises WithNow Option to ensure it performs as expected. | ||||
| func TestOptions_WithNow(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                time.Time | ||||
| 		IsErrorExpected      bool | ||||
| @@ -154,6 +160,8 @@ func TestOptions_WithNow(t *testing.T) { | ||||
|  | ||||
| // TestOptions_WithID exercises WithID Option to ensure it performs as expected. | ||||
| func TestOptions_WithID(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                string | ||||
| 		IsErrorExpected      bool | ||||
| @@ -199,6 +207,8 @@ func TestOptions_WithID(t *testing.T) { | ||||
|  | ||||
| // TestOptions_WithPrefix exercises WithPrefix Option to ensure it performs as expected. | ||||
| func TestOptions_WithPrefix(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                string | ||||
| 		IsErrorExpected      bool | ||||
| @@ -244,6 +254,8 @@ func TestOptions_WithPrefix(t *testing.T) { | ||||
|  | ||||
| // TestOptions_WithRaw exercises WithRaw Option to ensure it performs as expected. | ||||
| func TestOptions_WithRaw(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value         bool | ||||
| 		ExpectedValue bool | ||||
| @@ -274,6 +286,8 @@ func TestOptions_WithRaw(t *testing.T) { | ||||
|  | ||||
| // TestOptions_WithElision exercises WithElision Option to ensure it performs as expected. | ||||
| func TestOptions_WithElision(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value         bool | ||||
| 		ExpectedValue bool | ||||
| @@ -304,6 +318,8 @@ func TestOptions_WithElision(t *testing.T) { | ||||
|  | ||||
| // TestOptions_WithHMACAccessor exercises WithHMACAccessor Option to ensure it performs as expected. | ||||
| func TestOptions_WithHMACAccessor(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value         bool | ||||
| 		ExpectedValue bool | ||||
| @@ -334,6 +350,8 @@ func TestOptions_WithHMACAccessor(t *testing.T) { | ||||
|  | ||||
| // TestOptions_WithOmitTime exercises WithOmitTime Option to ensure it performs as expected. | ||||
| func TestOptions_WithOmitTime(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value         bool | ||||
| 		ExpectedValue bool | ||||
| @@ -365,6 +383,8 @@ func TestOptions_WithOmitTime(t *testing.T) { | ||||
| // TestOptions_WithHeaderFormatter exercises the WithHeaderFormatter Option to | ||||
| // ensure it applies the option as expected under various circumstances. | ||||
| func TestOptions_WithHeaderFormatter(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		Value                    HeaderFormatter | ||||
| 		ExpectedValue            HeaderFormatter | ||||
| @@ -403,6 +423,8 @@ func TestOptions_WithHeaderFormatter(t *testing.T) { | ||||
|  | ||||
| // TestOptions_Default exercises getDefaultOptions to assert the default values. | ||||
| func TestOptions_Default(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	opts := getDefaultOptions() | ||||
| 	require.NotNil(t, opts) | ||||
| 	require.True(t, time.Now().After(opts.withNow)) | ||||
| @@ -411,6 +433,8 @@ func TestOptions_Default(t *testing.T) { | ||||
|  | ||||
| // TestOptions_Opts exercises GetOpts with various Option values. | ||||
| func TestOptions_Opts(t *testing.T) { | ||||
| 	t.Parallel() | ||||
|  | ||||
| 	tests := map[string]struct { | ||||
| 		opts                 []Option | ||||
| 		IsErrorExpected      bool | ||||
|   | ||||
| @@ -6,61 +6,12 @@ package audit | ||||
| import ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/hashicorp/go-bexpr" | ||||
| 	"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 | ||||
|  | ||||
| // 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. | ||||
| @@ -94,14 +45,6 @@ type HeaderFormatter interface { | ||||
| 	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 | ||||
| @@ -144,13 +87,6 @@ type FormatterConfig struct { | ||||
| 	RequiredFormat format | ||||
| } | ||||
|  | ||||
| // EntryFilter should be used to filter audit requests and responses which should | ||||
| // make it to a sink. | ||||
| type EntryFilter struct { | ||||
| 	// the evaluator for the bexpr expression that should be applied by the node. | ||||
| 	evaluator *bexpr.Evaluator | ||||
| } | ||||
|  | ||||
| // RequestEntry is the structure of a request audit log entry. | ||||
| type RequestEntry struct { | ||||
| 	Time          string   `json:"time,omitempty"` | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Peter Wilson
					Peter Wilson