Skip to content

Commit

Permalink
[Auditbeat] normalized event fields for auditd module (#11432)
Browse files Browse the repository at this point in the history
The auditd module is updated to set normalized values for event.category
and event.type. It also sets event.outcome from auditd.result.

Currently it sets the following values for audit messages of
USER_LOGIN and USER_AUTH type:

| event.category | event.outcome |        event.type      |
|----------------|---------------|------------------------|
| authentication | success       | authentication_success |
| authentication | failure       | authentication_failure |

Closes #11428
  • Loading branch information
adriansr authored Apr 5, 2019
1 parent 22c4970 commit 0df354a
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 3 deletions.
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 @@ -95,6 +97,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

0 comments on commit 0df354a

Please sign in to comment.