Repository: elastic/go-libaudit (go)

Issue:
No support for setting enabled = 2, rules immutable


`SetEnabled` forces the value to be `1`, however the audit spec permits the value of enabled to be set to `2`, which prevents the audit rules from being changed.  This is usually enabled by calling `auditctl -e 2`, or adding `-e 2` to the audit rules on the system.  Because the rule parser in this library, and the library itself have no support for making the rules immutable, auditbeat is unable to parse any ruleset that makes the rules immutable (elastic/beats#8352)

https://github.com/elastic/go-libaudit/blob/db7d4996da54bed87ecf047e6c19d7656d237082/audit.go#L354-L365

Pull request description:
Add SetImmutable to audit client
SetImmutable is used to lock the audit configuration so that it can't be
changed. Locking the configuration is intended to be the last command you
issue. Any attempt to change the configuration in this mode will be
audited and denied. The configuration can only be changed by rebooting the
machine.

Closes #55

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d60317e..b409317 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,10 +14,13 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 - Added support for big endian. #48
 - Added semantic versioning support via go modules. #61
+- Add ECS categorization support for events by record type and syscall. #62
 - Added ECS categorization support for events by record type and syscall. #62
 - Fixed a typo in the action value associated with ROLE_REMOVE messages. #65
 - Fixed a typo in the action value associated with ANOM_LINK messages. #66
 - Fixed spelling of anomaly in aucoalesce package. #67
+- Added `SetImmutable` to the audit client for marking the audit settings
+  as immutable within the kernel. #55 #68
 
 ### Removed
 
diff --git a/audit.go b/audit.go
index c43ed41..beaca27 100644
--- a/audit.go
+++ b/audit.go
@@ -364,6 +364,19 @@ func (c *AuditClient) SetEnabled(enabled bool, wm WaitMode) error {
 	return c.set(status, wm)
 }
 
+// SetImmutable is used to lock the audit configuration so that it can't be
+// changed. Locking the configuration is intended to be the last command you
+// issue. Any attempt to change the configuration in this mode will be
+// audited and denied. The configuration can only be changed by rebooting the
+// machine.
+func (c *AuditClient) SetImmutable(wm WaitMode) error {
+	status := AuditStatus{
+		Mask:    AuditStatusEnabled,
+		Enabled: 2,
+	}
+	return c.set(status, wm)
+}
+
 // SetFailure sets the action that the kernel will perform when the backlog
 // limit is reached or when it encounters an error and cannot proceed.
 func (c *AuditClient) SetFailure(fm FailureMode, wm WaitMode) error {
@@ -587,7 +600,7 @@ var sizeofAuditStatus = int(unsafe.Sizeof(AuditStatus{}))
 // https://github.com/linux-audit/audit-kernel/blob/v4.7/include/uapi/linux/audit.h#L413-L427
 type AuditStatus struct {
 	Mask            AuditStatusMask // Bit mask for valid entries.
-	Enabled         uint32          // 1 = enabled, 0 = disabled
+	Enabled         uint32          // 1 = enabled, 0 = disabled, 2 = immutable
 	Failure         uint32          // Failure-to-log action.
 	PID             uint32          // PID of auditd process.
 	RateLimit       uint32          // Messages rate limit (per second).
diff --git a/cmd/audit/audit.go b/cmd/audit/audit.go
index 6baf3f2..272af48 100644
--- a/cmd/audit/audit.go
+++ b/cmd/audit/audit.go
@@ -42,6 +42,7 @@ var (
 	diag        = fs.String("diag", "", "dump raw information from kernel to file")
 	rate        = fs.Uint("rate", 0, "rate limit in kernel (default 0, no rate limit)")
 	backlog     = fs.Uint("backlog", 8192, "backlog limit")
+	immutable   = fs.Bool("immutable", false, "make kernel audit settings immutable (requires reboot to undo)")
 	receiveOnly = fs.Bool("ro", false, "receive only using multicast, requires kernel 3.16+")
 )
 
@@ -128,6 +129,13 @@ func read() error {
 			}
 		}
 
+		if status.Enabled != 2 {
+			log.Debugf("setting kernel settings as immutable")
+			if err = client.SetImmutable(libaudit.NoWait); err != nil {
+				return errors.Wrap(err, "failed to set kernel as immutable")
+			}
+		}
+
 		log.Debugf("sending message to kernel registering our PID (%v) as the audit daemon", os.Getpid())
 		if err = client.SetPID(libaudit.NoWait); err != nil {
 			return errors.Wrap(err, "failed to set audit PID")


Test patch:
diff --git a/audit_test.go b/audit_test.go
index 635670b..c403337 100644
--- a/audit_test.go
+++ b/audit_test.go
@@ -44,8 +44,9 @@ import (
 // docker run -it --rm -v `pwd`:/go/src/github.com/elastic/go-libaudit --pid=host --privileged golang:1.10.2 /bin/bash
 
 var (
-	hexdump = flag.Bool("hexdump", false, "dump kernel responses to stdout in hexdump -C format")
-	list    = flag.Bool("l", false, "dump rules")
+	hexdump   = flag.Bool("hexdump", false, "dump kernel responses to stdout in hexdump -C format")
+	list      = flag.Bool("l", false, "dump rules")
+	immutable = flag.Bool("immutable", false, "test setting kernel as immutable (requires reboot to undo)")
 )
 
 // testRule is a base64 representation of the following rule.
@@ -748,6 +749,39 @@ func TestAuditClientGetStatusAsync(t *testing.T) {
 	}
 }
 
+func TestAuditClientSetImmutable(t *testing.T) {
+	if os.Geteuid() != 0 {
+		t.Skip("must be root to enable audit")
+	}
+	if !*immutable {
+		t.Skip("use -immutable to test SetImmutable")
+	}
+
+	var dumper io.WriteCloser
+	if *hexdump {
+		dumper = hex.Dumper(os.Stdout)
+		defer dumper.Close()
+	}
+
+	c, err := NewAuditClient(dumper)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer c.Close()
+
+	err = c.SetImmutable(WaitForReply)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log("SetImmutable complete")
+
+	status, err := getStatus(t)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.EqualValues(t, 2, status.Enabled)
+}
+
 func TestRuleParsing(t *testing.T) {
 	var rules []string
 	switch runtime.GOARCH {