246 lines
7.5 KiB
Go
246 lines
7.5 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package auditd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/prometheus/procfs"
|
|
|
|
"github.com/elastic/beats/auditbeat/core"
|
|
"github.com/elastic/beats/libbeat/logp"
|
|
"github.com/elastic/beats/metricbeat/mb"
|
|
mbtest "github.com/elastic/beats/metricbeat/mb/testing"
|
|
"github.com/elastic/go-libaudit"
|
|
"github.com/elastic/go-libaudit/auparse"
|
|
)
|
|
|
|
// Specify the -audit flag when running these tests to interact with the real
|
|
// kernel instead of mocks. If running in Docker this requires being in the
|
|
// host PID namespace (--pid=host) and having CAP_AUDIT_CONTROL and
|
|
// CAP_AUDIT_WRITE (so use --privileged).
|
|
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'`
|
|
|
|
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"`,
|
|
`type=EXECVE msg=audit(1492752522.985:8972): argc=2 a0="uname" a1="-a"`,
|
|
`type=CWD msg=audit(1492752522.985:8972): cwd="/home/andrew_kroh"`,
|
|
`type=PATH msg=audit(1492752522.985:8972): item=0 name="/bin/uname" inode=155 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL`,
|
|
`type=PATH msg=audit(1492752522.985:8972): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=1923 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL`,
|
|
`type=PROCTITLE msg=audit(1492752522.985:8972): proctitle=756E616D65002D61`,
|
|
`type=EOE msg=audit(1492752522.985:8972):`,
|
|
}
|
|
|
|
acceptMsgs = []string{
|
|
`type=SYSCALL msg=audit(1492752520.441:8832): arch=c000003e syscall=43 success=yes exit=5 a0=3 a1=7ffd0dc80040 a2=7ffd0dc7ffd0 a3=0 items=0 ppid=1 pid=1663 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="sshd" exe="/usr/sbin/sshd" key="key=net"`,
|
|
`type=SOCKADDR msg=audit(1492752520.441:8832): saddr=0200E31C4853E6640000000000000000`,
|
|
`type=PROCTITLE msg=audit(1492752520.441:8832): proctitle="(sshd)"`,
|
|
`type=EOE msg=audit(1492752520.441:8832):`,
|
|
}
|
|
)
|
|
|
|
func TestData(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 a single audit message from the kernel.
|
|
returnMessage(userLoginMsg)
|
|
|
|
// 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, 1, ms)
|
|
if len(events) == 0 {
|
|
t.Fatal("received no events")
|
|
}
|
|
assertNoErrors(t, events)
|
|
|
|
beatEvent := mbtest.StandardizeEvent(ms, events[0], core.AddDatasetToEvent)
|
|
mbtest.WriteEventToDataJSON(t, beatEvent, "")
|
|
}
|
|
|
|
func getConfig() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"module": "auditd",
|
|
"failure_mode": "log",
|
|
"socket_type": "unicast",
|
|
}
|
|
}
|
|
|
|
func TestUnicastClient(t *testing.T) {
|
|
if !*audit {
|
|
t.Skip("-audit was not specified")
|
|
}
|
|
|
|
logp.TestingSetup()
|
|
FailIfAuditdIsRunning(t)
|
|
|
|
c := map[string]interface{}{
|
|
"module": "auditd",
|
|
"socket_type": "unicast",
|
|
"audit_rules": fmt.Sprintf(`
|
|
-a always,exit -F arch=b64 -F ppid=%d -S execve -k exec
|
|
`, os.Getpid()),
|
|
}
|
|
|
|
// Any commands executed by this process will generate events due to the
|
|
// PPID filter we applied to the rule.
|
|
time.AfterFunc(time.Second, func() { exec.Command("cat", "/proc/self/status").Output() })
|
|
|
|
ms := mbtest.NewPushMetricSetV2(t, c)
|
|
events := mbtest.RunPushMetricSetV2(5*time.Second, 0, ms)
|
|
assertNoErrors(t, events)
|
|
assertHasBinCatExecve(t, events)
|
|
}
|
|
|
|
func TestMulticastClient(t *testing.T) {
|
|
if !*audit {
|
|
t.Skip("-audit was not specified")
|
|
}
|
|
|
|
if !hasMulticastSupport() {
|
|
t.Skip("no multicast support")
|
|
}
|
|
|
|
logp.TestingSetup()
|
|
FailIfAuditdIsRunning(t)
|
|
|
|
c := map[string]interface{}{
|
|
"module": "auditd",
|
|
"socket_type": "multicast",
|
|
"audit_rules": fmt.Sprintf(`
|
|
-a always,exit -F arch=b64 -F ppid=%d -S execve -k exec
|
|
`, os.Getpid()),
|
|
}
|
|
|
|
// Any commands executed by this process will generate events due to the
|
|
// PPID filter we applied to the rule.
|
|
time.AfterFunc(time.Second, func() { exec.Command("cat", "/proc/self/status").Output() })
|
|
|
|
ms := mbtest.NewPushMetricSetV2(t, c)
|
|
events := mbtest.RunPushMetricSetV2(5*time.Second, 0, ms)
|
|
assertNoErrors(t, events)
|
|
assertHasBinCatExecve(t, events)
|
|
}
|
|
|
|
func TestKernelVersion(t *testing.T) {
|
|
major, minor, full, err := kernelVersion()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("major=%v, minor=%v, full=%v", major, minor, full)
|
|
}
|
|
|
|
func FailIfAuditdIsRunning(t testing.TB) {
|
|
t.Helper()
|
|
|
|
procs, err := procfs.AllProcs()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, proc := range procs {
|
|
comm, err := proc.Comm()
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
|
|
if comm == "auditd" {
|
|
t.Fatalf("auditd is running (pid=%d). This test cannot run while "+
|
|
"auditd is running.", proc.PID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildMetricbeatEvent(t *testing.T) {
|
|
if f := flag.Lookup("data"); f != nil && f.Value.String() == "false" {
|
|
t.Skip("skip data generation tests")
|
|
}
|
|
buildSampleEvent(t, acceptMsgs, "_meta/accept.json")
|
|
buildSampleEvent(t, execveMsgs, "_meta/execve.json")
|
|
}
|
|
|
|
func buildSampleEvent(t testing.TB, lines []string, filename string) {
|
|
var msgs []*auparse.AuditMessage
|
|
for _, txt := range lines {
|
|
m, err := auparse.ParseLogLine(txt)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
msgs = append(msgs, m)
|
|
}
|
|
|
|
e := buildMetricbeatEvent(msgs, defaultConfig)
|
|
beatEvent := e.BeatEvent(moduleName, metricsetName, core.AddDatasetToEvent)
|
|
output, err := json.MarshalIndent(&beatEvent.Fields, "", " ")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := ioutil.WriteFile(filename, output, 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func assertHasBinCatExecve(t *testing.T, events []mb.Event) {
|
|
t.Helper()
|
|
|
|
for _, e := range events {
|
|
v, err := e.RootFields.GetValue("process.exe")
|
|
if err == nil {
|
|
if exe, ok := v.(string); ok && exe == "/bin/cat" {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
assert.Fail(t, "expected an execve event for /bin/cat")
|
|
}
|
|
|
|
func assertNoErrors(t *testing.T, events []mb.Event) {
|
|
t.Helper()
|
|
|
|
for _, e := range events {
|
|
t.Log(e)
|
|
|
|
if e.Error != nil {
|
|
t.Errorf("received error: %+v", e.Error)
|
|
}
|
|
}
|
|
}
|