Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions receiver/bindplaneauditlogs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ This receiver is capable of collecting audit logs from a Bindplane instance.

## Configuration

| Field | Type | Default | Required | Description |
| ------------- | ------ | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| api_key | string | | `true` | The Bindplane API key with read access to audit logs. This API key has access to the audit logs of a single project. |
| endpoint | string | | `true` | The endpoint to collect logs from. (e.g. `https://app.bindplane.com`) |
| poll_interval | string | 10s | `false` | The rate at which this receiver will poll Bindplane for logs. This value must be in the range [10 seconds - 24 hours] and must be a string readable by Golang's [time.ParseDuration](https://pkg.go.dev/time#ParseDuration). |
| Field | Type | Default | Required | Description |
| ---------------- | ------ | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| api_key | string | | `true` | The Bindplane API key with read access to audit logs. This API key has access to the audit logs of a single project. |
| endpoint | string | | `true` | The endpoint to collect logs from. (e.g. `https://app.bindplane.com`) |
| poll_interval | string | 10s | `false` | The rate at which this receiver will poll Bindplane for logs. This value must be in the range [10 seconds - 24 hours] and must be a string readable by Golang's [time.ParseDuration](https://pkg.go.dev/time#ParseDuration). |
| parse_attributes | bool | true | `false` | When true, parses audit log fields into log record attributes and sets the body to the description. When false, sets the body to the raw JSON event without attributes. |

### Example Configuration

Expand Down
4 changes: 4 additions & 0 deletions receiver/bindplaneauditlogs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type Config struct {
// PollInterval is the interval at which the receiver polls for new audit logs
PollInterval time.Duration `mapstructure:"poll_interval"`

// ParseAttributes when true parses the audit log fields into log record attributes
// and sets the body to the description. When false, the body is set to the raw JSON event.
ParseAttributes bool `mapstructure:"parse_attributes"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could optionally add a small test here in config_test.go, just to reiterate the intended default behavior. Similar tests seen in other receivers.

func TestCreateDefaultConfig(t *testing.T) {
	cfg := createDefaultConfig().(*Config)

	require.Equal(t, 10*time.Second, cfg.PollInterval)
	require.True(t, cfg.ParseAttributes)
}

// bindplaneURL is the URL to the BindPlane audit logs API. Taken from the client config endpoint.
bindplaneURL *url.URL
}
Expand Down
6 changes: 6 additions & 0 deletions receiver/bindplaneauditlogs/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,9 @@ func TestValidate(t *testing.T) {
})
}
}

func TestCreateDefaultConfig(t *testing.T) {
cfg := createDefaultConfig().(*Config)
require.Equal(t, true, cfg.ParseAttributes)
require.Equal(t, 10*time.Second, cfg.PollInterval)
}
3 changes: 2 additions & 1 deletion receiver/bindplaneauditlogs/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func createLogsReceiver(

func createDefaultConfig() component.Config {
return &Config{
PollInterval: 10 * time.Second,
PollInterval: 10 * time.Second,
ParseAttributes: true,
}
}
46 changes: 27 additions & 19 deletions receiver/bindplaneauditlogs/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,27 +197,35 @@ func (r *bindplaneAuditLogsReceiver) processLogEvents(observedTime pcommon.Times
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(*logEvent.Timestamp))
}

// Set body as description
if logEvent.Description != "" {
logRecord.Body().SetStr(logEvent.Description)
}
// Only set attributes if parsing is enabled
if r.cfg.ParseAttributes {
Comment thread
justinianvoss22 marked this conversation as resolved.
//
if logEvent.Description != "" {
logRecord.Body().SetStr(logEvent.Description)
}
// Set attributes based on the Bindplane audit log format
attrs := logRecord.Attributes()
attrs.PutStr("id", logEvent.ID)
if logEvent.Timestamp != nil {
attrs.PutStr("timestamp", logEvent.Timestamp.Format(bindplaneTimeFormat))
}
attrs.PutStr("resource_name", logEvent.ResourceName)
attrs.PutStr("resource_kind", string(logEvent.ResourceKind))
if logEvent.Configuration != "" {
attrs.PutStr("configuration", logEvent.Configuration)
}
attrs.PutStr("action", string(logEvent.Action))
attrs.PutStr("user", logEvent.User)

// Set attributes based on the Bindplane audit log format
attrs := logRecord.Attributes()
attrs.PutStr("id", logEvent.ID)
if logEvent.Timestamp != nil {
attrs.PutStr("timestamp", logEvent.Timestamp.Format(bindplaneTimeFormat))
}
attrs.PutStr("resource_name", logEvent.ResourceName)
attrs.PutStr("resource_kind", string(logEvent.ResourceKind))
if logEvent.Configuration != "" {
attrs.PutStr("configuration", logEvent.Configuration)
resourceAttrs.PutStr("account", logEvent.Account)
} else {
eventBytes, err := json.Marshal(logEvent)
if err != nil {
r.logger.Error("unable to marshal logEvent", zap.Error(err))
} else {
logRecord.Body().SetStr(string(eventBytes))
}
}
attrs.PutStr("action", string(logEvent.Action))
attrs.PutStr("user", logEvent.User)

resourceAttrs.PutStr("account", logEvent.Account)

}

return logs
Expand Down
47 changes: 47 additions & 0 deletions receiver/bindplaneauditlogs/logs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ func TestProcessLogEvents(t *testing.T) {
require.Equal(t, 1, logs.LogRecordCount())
logRecord := logs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0)

// Verify body is description by default
require.Equal(t, "test description", logRecord.Body().Str())

// Verify attributes
attrs := logRecord.Attributes()
id, _ := attrs.Get("id")
Expand All @@ -232,6 +235,50 @@ func TestProcessLogEvents(t *testing.T) {
require.Equal(t, "test-resource", resourceName.Str())
}

func TestProcessLogEventsNoParseAttributes(t *testing.T) {
cfg := createDefaultConfig().(*Config)
cfg.ParseAttributes = false
recv := newReceiver(t, cfg, consumertest.NewNop())

now := time.Now().UTC()
testEvents := []AuditLogEvent{
{
ID: "1",
Timestamp: &now,
ResourceName: "test-resource",
Description: "test description",
ResourceKind: "Source",
Configuration: "test-config",
Action: "Created",
User: "test-user",
Account: "test-account",
},
}

logs := recv.processLogEvents(pcommon.NewTimestampFromTime(now), testEvents)

require.Equal(t, 1, logs.LogRecordCount())
logRecord := logs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0)

// Verify body is raw JSON when ParseAttributes is disabled
body := logRecord.Body().Str()
require.Contains(t, body, `"id":"1"`)
require.Contains(t, body, `"resourceName":"test-resource"`)
require.Contains(t, body, `"description":"test description"`)
require.Contains(t, body, `"resourceKind":"Source"`)
require.Contains(t, body, `"configuration":"test-config"`)
require.Contains(t, body, `"action":"Created"`)
require.Contains(t, body, `"user":"test-user"`)
require.Contains(t, body, `"account":"test-account"`)

// Verify the body can be unmarshaled back to an event
var parsedEvent AuditLogEvent
err := json.Unmarshal([]byte(body), &parsedEvent)
require.NoError(t, err)
require.Equal(t, "1", parsedEvent.ID)
require.Equal(t, "test-resource", parsedEvent.ResourceName)
}

func TestLastTimestampUpdate(t *testing.T) {
cfg := createDefaultConfig().(*Config)
cfg.bindplaneURL = &url.URL{
Expand Down