Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Auditbeat] normalized event.type/category/outcome fields for auditd module #11432

Merged
merged 1 commit into from
Apr 5, 2019
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
4 changes: 4 additions & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d

*Auditbeat*

- Auditd module: Normalized value of `event.category` field from `user-login` to `authentication`. {pull}11432[11432]

*Filebeat*

*Heartbeat*
Expand Down Expand Up @@ -93,6 +95,8 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d

*Auditbeat*

- Auditd module: Add `event.outcome` and `event.type` for ECS. {pull}11432[11432]

*Filebeat*

- Add more info to message logged when a duplicated symlink file is found {pull}10845[10845]
Expand Down
30 changes: 30 additions & 0 deletions auditbeat/module/auditd/audit_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,12 +467,17 @@ func buildMetricbeatEvent(msgs []*auparse.AuditMessage, config Config) mb.Event
aucoalesce.ResolveIDs(auditEvent)
}

eventOutcome := auditEvent.Result
if eventOutcome == "fail" {
eventOutcome = "failure"
}
out := mb.Event{
Timestamp: auditEvent.Timestamp,
RootFields: common.MapStr{
"event": common.MapStr{
"category": auditEvent.Category.String(),
"action": auditEvent.Summary.Action,
"outcome": eventOutcome,
},
},
ModuleFields: common.MapStr{
Expand All @@ -484,6 +489,9 @@ func buildMetricbeatEvent(msgs []*auparse.AuditMessage, config Config) mb.Event
},
}

// Customize event.type / event.category to match unified values.
normalizeEventFields(out.RootFields)

// Add root level fields.
addUser(auditEvent.User, out.RootFields)
addProcess(auditEvent.Process, out.RootFields)
Expand Down Expand Up @@ -533,6 +541,28 @@ func buildMetricbeatEvent(msgs []*auparse.AuditMessage, config Config) mb.Event
return out
}

func normalizeEventFields(m common.MapStr) {
getFieldAsStr := func(key string) (s string, found bool) {
iface, err := m.GetValue(key)
if err != nil {
return
}
s, found = iface.(string)
return
}

category, ok1 := getFieldAsStr("event.category")
action, ok2 := getFieldAsStr("event.action")
outcome, ok3 := getFieldAsStr("event.outcome")
if !ok1 || !ok2 || !ok3 {
return
}
if category == "user-login" && action == "logged-in" { // USER_LOGIN
m.Put("event.category", "authentication")
m.Put("event.type", fmt.Sprintf("authentication_%s", outcome))
}
}

func addUser(u aucoalesce.User, m common.MapStr) {
user := common.MapStr{}
m.Put("user", user)
Expand Down
63 changes: 60 additions & 3 deletions auditbeat/module/auditd/audit_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/prometheus/procfs"

"github.com/elastic/beats/auditbeat/core"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/logp"
"github.com/elastic/beats/libbeat/mapping"
"github.com/elastic/beats/metricbeat/mb"
Expand All @@ -48,7 +49,8 @@ import (
var audit = flag.Bool("audit", false, "interact with the real audit framework")

var (
userLoginMsg = `type=USER_LOGIN msg=audit(1492896301.818:19955): pid=12635 uid=0 auid=4294967295 ses=4294967295 msg='op=login acct=28696E76616C6964207573657229 exe="/usr/sbin/sshd" hostname=? addr=179.38.151.221 terminal=sshd res=failed'`
userLoginFailMsg = `type=USER_LOGIN msg=audit(1492896301.818:19955): pid=12635 uid=0 auid=4294967295 ses=4294967295 msg='op=login acct=28696E76616C6964207573657229 exe="/usr/sbin/sshd" hostname=? addr=179.38.151.221 terminal=sshd res=failed'`
userLoginSuccessMsg = `type=USER_LOGIN msg=audit(1492896303.915:19956): pid=12635 uid=0 auid=4294967295 ses=4294967295 msg='op=login acct=28696E76616C6964207573657229 exe="/usr/sbin/sshd" hostname=? addr=179.38.151.221 terminal=sshd res=success'`

execveMsgs = []string{
`type=SYSCALL msg=audit(1492752522.985:8972): arch=c000003e syscall=59 success=yes exit=0 a0=10812c8 a1=1070208 a2=1152008 a3=59a items=2 ppid=10027 pid=10043 auid=1001 uid=1001 gid=1002 euid=1001 suid=1001 fsuid=1001 egid=1002 sgid=1002 fsgid=1002 tty=pts0 ses=11 comm="uname" exe="/bin/uname" key="key=user_commands"`,
Expand Down Expand Up @@ -77,8 +79,8 @@ func TestData(t *testing.T) {
returnACK().returnStatus().
// Send expected ACKs for initialization
returnACK().returnACK().returnACK().returnACK().returnACK().
// Send a single audit message from the kernel.
returnMessage(userLoginMsg).
// Send three auditd messages.
returnMessage(userLoginFailMsg).
returnMessage(execveMsgs...).
returnMessage(acceptMsgs...)

Expand All @@ -100,6 +102,61 @@ func TestData(t *testing.T) {
mbtest.WriteEventToDataJSON(t, beatEvent, "")
}

func TestLoginType(t *testing.T) {
logp.TestingSetup()

// Create a mock netlink client that provides the expected responses.
mock := NewMock().
// Get Status response for initClient
returnACK().returnStatus().
// Send expected ACKs for initialization
returnACK().returnACK().returnACK().returnACK().returnACK().
// Send an authentication failure and a success.
returnMessage(userLoginFailMsg).
returnMessage(userLoginSuccessMsg)

// Replace the default AuditClient with a mock.
ms := mbtest.NewPushMetricSetV2(t, getConfig())
auditMetricSet := ms.(*MetricSet)
auditMetricSet.client.Close()
auditMetricSet.client = &libaudit.AuditClient{Netlink: mock}

events := mbtest.RunPushMetricSetV2(10*time.Second, 2, ms)
if len(events) != 2 {
t.Fatalf("expected 2 events, but received %d", len(events))
}
assertNoErrors(t, events)

assertFieldsAreDocumented(t, events)

// Sometimes the events are received in reverse order.
if events[0].ModuleFields["sequence"].(uint32) > events[1].ModuleFields["sequence"].(uint32) {
events[0], events[1] = events[1], events[0]
}

for idx, expected := range []common.MapStr{
{
"event.category": "authentication",
"event.type": "authentication_failure",
"event.outcome": "failure",
},
{
"event.category": "authentication",
"event.type": "authentication_success",
"event.outcome": "success",
},
} {
beatEvent := mbtest.StandardizeEvent(ms, events[idx], core.AddDatasetToEvent)
mbtest.WriteEventToDataJSON(t, beatEvent, "")
for k, v := range expected {
msg := fmt.Sprintf("%s[%d]", k, idx)
cur, err := beatEvent.GetValue(k)
assert.NoError(t, err, msg)
assert.Equal(t, v, cur, msg)
}
}
}

// assertFieldsAreDocumented mimics assert_fields_are_documented in Python system tests.
func assertFieldsAreDocumented(t *testing.T, events []mb.Event) {
fieldsYml, err := mapping.LoadFieldsYaml("../../fields.yml")
Expand Down