Repository: aquasecurity/kube-bench (go)

Issue:
The etcd data directory related checkes support relative path
https://github.com/aquasecurity/kube-bench/blob/77f66511e70c4b2c1f83988786e3de04ec7349db/cfg/cis-1.5/master.yaml#L283-L292

How about support the case that the value of `--data-dir` is relative path:

```
etcd --data-dir=data.etcd ...
```

Pull request description:
support customize datadir locations of etcd
Use this way to support cases:

* The etcd server startup with no `--data-dir` flag,  it will use default value(`${name}.etcd`) as data-dir
* The value of `--data-dir`  flag is a relative path. Closes #583

refer:

* https://etcd.io/docs/v3.5/op-guide/configuration/#command-line-flags

Patch:
diff --git a/cfg/cis-1.20/master.yaml b/cfg/cis-1.20/master.yaml
index fcbfcd4..10e5028 100644
--- a/cfg/cis-1.20/master.yaml
+++ b/cfg/cis-1.20/master.yaml
@@ -153,7 +153,13 @@ groups:
 
       - id: 1.1.11
         text: "Ensure that the etcd data directory permissions are set to 700 or more restrictive (Automated)"
-        audit: ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%' | xargs stat -c permissions=%a
+        audit: |
+          DATA_DIR=''
+          for d in $(ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%'); do
+            if test -d "$d"; then DATA_DIR="$d"; fi
+          done
+          if ! test -d "$DATA_DIR"; then DATA_DIR=$etcddatadir; fi
+          stat -c permissions=%a "$DATA_DIR"
         tests:
           test_items:
             - flag: "permissions"
diff --git a/cfg/cis-1.23/master.yaml b/cfg/cis-1.23/master.yaml
index c77c9a1..8ed17af 100644
--- a/cfg/cis-1.23/master.yaml
+++ b/cfg/cis-1.23/master.yaml
@@ -147,7 +147,13 @@ groups:
 
       - id: 1.1.11
         text: "Ensure that the etcd data directory permissions are set to 700 or more restrictive (Automated)"
-        audit: ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%' | xargs stat -c permissions=%a
+        audit: |
+          DATA_DIR=''
+          for d in $(ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%'); do
+            if test -d "$d"; then DATA_DIR="$d"; fi
+          done
+          if ! test -d "$DATA_DIR"; then DATA_DIR=$etcddatadir; fi
+          stat -c permissions=%a "$DATA_DIR"
         tests:
           test_items:
             - flag: "permissions"
diff --git a/cfg/cis-1.5/master.yaml b/cfg/cis-1.5/master.yaml
index 926cc32..df0b491 100644
--- a/cfg/cis-1.5/master.yaml
+++ b/cfg/cis-1.5/master.yaml
@@ -158,7 +158,13 @@ groups:
 
       - id: 1.1.11
         text: "Ensure that the etcd data directory permissions are set to 700 or more restrictive (Scored)"
-        audit: ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%' | xargs stat -c permissions=%a
+        audit: |
+          DATA_DIR=''
+          for d in $(ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%'); do
+            if test -d "$d"; then DATA_DIR="$d"; fi
+          done
+          if ! test -d "$DATA_DIR"; then DATA_DIR=$etcddatadir; fi
+          stat -c permissions=%a "$DATA_DIR"
         tests:
           test_items:
             - flag: "permissions"
@@ -176,7 +182,13 @@ groups:
 
       - id: 1.1.12
         text: "Ensure that the etcd data directory ownership is set to etcd:etcd (Scored)"
-        audit: ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%' | xargs stat -c %U:%G
+        audit: |
+          DATA_DIR=''
+          for d in $(ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%'); do
+            if test -d "$d"; then DATA_DIR="$d"; fi
+          done
+          if ! test -d "$DATA_DIR"; then DATA_DIR=$etcddatadir; fi
+          stat -c %U:%G $DATA_DIR
         tests:
           test_items:
             - flag: "etcd:etcd"
diff --git a/cfg/cis-1.6/master.yaml b/cfg/cis-1.6/master.yaml
index 59d5e0b..85ed523 100644
--- a/cfg/cis-1.6/master.yaml
+++ b/cfg/cis-1.6/master.yaml
@@ -153,7 +153,13 @@ groups:
 
       - id: 1.1.11
         text: "Ensure that the etcd data directory permissions are set to 700 or more restrictive (Automated)"
-        audit: ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%' | xargs stat -c permissions=%a
+        audit: |
+          DATA_DIR=''
+          for d in $(ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%'); do
+            if test -d "$d"; then DATA_DIR="$d"; fi
+          done
+          if ! test -d "$DATA_DIR"; then DATA_DIR=$etcddatadir; fi
+          stat -c permissions=%a "$DATA_DIR"
         tests:
           test_items:
             - flag: "permissions"
@@ -170,7 +176,13 @@ groups:
 
       - id: 1.1.12
         text: "Ensure that the etcd data directory ownership is set to etcd:etcd (Automated)"
-        audit: ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%' | xargs stat -c %U:%G
+        audit: |
+          DATA_DIR=''
+          for d in $(ps -ef | grep $etcdbin | grep -- --data-dir | sed 's%.*data-dir[= ]\([^ ]*\).*%\1%'); do
+            if test -d "$d"; then DATA_DIR="$d"; fi
+          done
+          if ! test -d "$DATA_DIR"; then DATA_DIR=$etcddatadir; fi
+          stat -c %U:%G $DATA_DIR
         tests:
           test_items:
             - flag: "etcd:etcd"
diff --git a/cfg/config.yaml b/cfg/config.yaml
index ad24177..306c875 100644
--- a/cfg/config.yaml
+++ b/cfg/config.yaml
@@ -89,6 +89,9 @@ master:
     bins:
       - "etcd"
       - "openshift start etcd"
+    datadirs:
+      - /var/lib/etcd/default.etcd
+      - /var/lib/etcd/data.etcd
     confs:
       - /etc/kubernetes/manifests/etcd.yaml
       - /etc/kubernetes/manifests/etcd.yml
@@ -99,6 +102,7 @@ master:
       - /var/snap/microk8s/current/args/etcd
       - /usr/lib/systemd/system/etcd.service
     defaultconf: /etc/kubernetes/manifests/etcd.yaml
+    defaultdatadir: /var/lib/etcd/default.etcd
 
   flanneld:
     optional: true
@@ -211,6 +215,9 @@ etcd:
   etcd:
     bins:
       - "etcd"
+    datadirs:
+      - /var/lib/etcd/default.etcd
+      - /var/lib/etcd/data.etcd
     confs:
       - /etc/kubernetes/manifests/etcd.yaml
       - /etc/kubernetes/manifests/etcd.yml
@@ -221,6 +228,7 @@ etcd:
       - /var/snap/microk8s/current/args/etcd
       - /usr/lib/systemd/system/etcd.service
     defaultconf: /etc/kubernetes/manifests/etcd.yaml
+    defaultdatadir: /var/lib/etcd/default.etcd
 
 controlplane:
   components:
diff --git a/cmd/common.go b/cmd/common.go
index cf29794..dc447e8 100644
--- a/cmd/common.go
+++ b/cmd/common.go
@@ -96,6 +96,7 @@ func runChecks(nodetype check.NodeType, testYamlFile, detectedVersion string) {
 	svcmap := getFiles(typeConf, "service")
 	kubeconfmap := getFiles(typeConf, "kubeconfig")
 	cafilemap := getFiles(typeConf, "ca")
+	datadirmap := getFiles(typeConf, "datadir")
 
 	// Variable substitutions. Replace all occurrences of variables in controls files.
 	s := string(in)
@@ -104,6 +105,7 @@ func runChecks(nodetype check.NodeType, testYamlFile, detectedVersion string) {
 	s, _ = makeSubstitutions(s, "svc", svcmap)
 	s, _ = makeSubstitutions(s, "kubeconfig", kubeconfmap)
 	s, _ = makeSubstitutions(s, "cafile", cafilemap)
+	s, _ = makeSubstitutions(s, "datadir", datadirmap)
 
 	controls, err := check.NewControls(nodetype, []byte(s), detectedVersion)
 	if err != nil {
diff --git a/cmd/util.go b/cmd/util.go
index 2ab2cca..d67d854 100644
--- a/cmd/util.go
+++ b/cmd/util.go
@@ -33,6 +33,7 @@ var (
 		"kubeconfig": {"kubeconfig", "defaultkubeconfig"},
 		"service":    {"svc", "defaultsvc"},
 		"config":     {"confs", "defaultconf"},
+		"datadir":    {"datadirs", "defaultdatadir"},
 	}
 )
 


Test patch:
diff --git a/cmd/util_test.go b/cmd/util_test.go
index 7ced42a..ecdd04c 100644
--- a/cmd/util_test.go
+++ b/cmd/util_test.go
@@ -395,6 +395,58 @@ func TestGetServiceFiles(t *testing.T) {
 	}
 }
 
+func TestGetDatadirFiles(t *testing.T) {
+	var err error
+	datadir, err := ioutil.TempDir("", "kube-bench-test-etcd-data-dir")
+	if err != nil {
+		t.Fatalf("Failed to create temp directory")
+	}
+	defer os.RemoveAll(datadir)
+
+	cases := []struct {
+		config      map[string]interface{}
+		exp         map[string]string
+		statResults []error
+	}{
+		{
+			config: map[string]interface{}{
+				"components": []string{"etcd"},
+				"etcd": map[string]interface{}{"datadirs": []string{datadir},
+					"defaultdatadir": "/var/lib/etcd/default.etcd"},
+			},
+			statResults: []error{nil},
+			exp:         map[string]string{"etcd": datadir},
+		},
+		// fallback to defaultdatadir
+		{
+			config: map[string]interface{}{
+				"components": []string{"etcd"},
+				"etcd": map[string]interface{}{"datadirs": []string{"/path/to/etcd/data.etcd"},
+					"defaultdatadir": "/var/lib/etcd/default.etcd"},
+			},
+			statResults: []error{os.ErrNotExist},
+			exp:         map[string]string{"etcd": "/var/lib/etcd/default.etcd"},
+		},
+	}
+
+	v := viper.New()
+	statFunc = fakestat
+
+	for id, c := range cases {
+		t.Run(strconv.Itoa(id), func(t *testing.T) {
+			for k, val := range c.config {
+				v.Set(k, val)
+			}
+			e = c.statResults
+			eIndex = 0
+			m := getFiles(v, "datadir")
+			if !reflect.DeepEqual(m, c.exp) {
+				t.Fatalf("Got %v\nExpected %v", m, c.exp)
+			}
+		})
+	}
+}
+
 func TestMakeSubsitutions(t *testing.T) {
 	cases := []struct {
 		input        string