From 76dd9bfcc14f2a77dd9a8155e66434a304cd0c38 Mon Sep 17 00:00:00 2001
From: Pawel Wieczorek
Date: Thu, 26 Sep 2019 16:43:01 +0200
Subject: k8s: Extract common interface to simplify development
Common command and service name extraction is intended to limit
execution to small set of allowed processes.
This patch also drops unnecessary use of "Kubernetes" name because this
whole subproject concerns its clusters.
Issue-ID: SECCOM-235
Change-Id: I8babfeb4f24cf3baa4d236ca622c21170ab6205e
Signed-off-by: Pawel Wieczorek
---
test/security/k8s/src/check/check.go | 45 ++++++++++
test/security/k8s/src/check/cmd/check/check.go | 14 ++--
test/security/k8s/src/check/rancher/rancher.go | 36 +++++---
test/security/k8s/src/check/raw/raw.go | 31 ++++---
.../k8s/src/check/validators/master/master.go | 97 +++++++++++-----------
5 files changed, 142 insertions(+), 81 deletions(-)
create mode 100644 test/security/k8s/src/check/check.go
(limited to 'test/security')
diff --git a/test/security/k8s/src/check/check.go b/test/security/k8s/src/check/check.go
new file mode 100644
index 000000000..c185887d7
--- /dev/null
+++ b/test/security/k8s/src/check/check.go
@@ -0,0 +1,45 @@
+package check
+
+// Informer collects and returns information on cluster.
+type Informer interface {
+ // GetAPIParams returns API server parameters.
+ GetAPIParams() ([]string, error)
+}
+
+// Command represents commands run on cluster.
+type Command int
+
+const (
+ // APIProcess represents API server command ("kube-apiserver").
+ APIProcess Command = iota
+)
+
+func (c Command) String() string {
+ names := [...]string{
+ "kube-apiserver",
+ }
+
+ if c < APIProcess || c > APIProcess {
+ return "exit"
+ }
+ return names[c]
+}
+
+// Service represents services run on Rancher-based cluster.
+type Service int
+
+const (
+ // APIService represents API server service ("kubernetes/kubernetes").
+ APIService Service = iota
+)
+
+func (s Service) String() string {
+ names := [...]string{
+ "kubernetes/kubernetes",
+ }
+
+ if s < APIService || s > APIService {
+ return ""
+ }
+ return names[s]
+}
diff --git a/test/security/k8s/src/check/cmd/check/check.go b/test/security/k8s/src/check/cmd/check/check.go
index 80a17f0c5..40e3a092c 100644
--- a/test/security/k8s/src/check/cmd/check/check.go
+++ b/test/security/k8s/src/check/cmd/check/check.go
@@ -4,6 +4,7 @@ import (
"flag"
"log"
+ "check"
"check/rancher"
"check/raw"
"check/validators/master"
@@ -25,23 +26,20 @@ func main() {
*rke = true
}
- var (
- k8sParams []string
- err error
- )
+ var info check.Informer
switch {
case *ranchercli:
- k8sParams, err = rancher.GetK8sParams()
+ info = &rancher.Rancher{}
case *rke:
- k8sParams, err = raw.GetK8sParams()
+ info = &raw.Raw{}
default:
log.Fatal("Missing cluster access method.")
}
+ apiParams, err := info.GetAPIParams()
if err != nil {
log.Fatal(err)
}
-
- master.Check(k8sParams)
+ master.CheckAPI(apiParams)
}
diff --git a/test/security/k8s/src/check/rancher/rancher.go b/test/security/k8s/src/check/rancher/rancher.go
index d60b73b65..d77f15445 100644
--- a/test/security/k8s/src/check/rancher/rancher.go
+++ b/test/security/k8s/src/check/rancher/rancher.go
@@ -3,8 +3,10 @@ package rancher
import (
"bytes"
- "errors"
+ "fmt"
"os/exec"
+
+ "check"
)
const (
@@ -16,32 +18,40 @@ const (
cmdDockerCmdPs = "ps"
cmdDockerCmdPsParams = "--no-trunc"
cmdDockerCmdPsFilter = "--filter"
- cmdDockerCmdPsFilterArgs = "label=io.rancher.stack_service.name=kubernetes/kubernetes"
+ cmdDockerCmdPsFilterArgs = "label=io.rancher.stack_service.name="
cmdDockerCmdPsFormat = "--format"
cmdDockerCmdPsFormatArgs = "{{.Command}}"
- k8sProcess = "kube-apiserver"
)
-// GetK8sParams returns parameters of running Kubernetes API server.
+// Rancher implements Informer interface.
+type Rancher struct {
+ check.Informer
+}
+
+// GetAPIParams returns parameters of running Kubernetes API server.
// It queries default environment set in configuration file.
-func GetK8sParams() ([]string, error) {
+func (r *Rancher) GetAPIParams() ([]string, error) {
+ return getProcessParams(check.APIProcess, check.APIService)
+}
+
+func getProcessParams(process check.Command, service check.Service) ([]string, error) {
hosts, err := listHosts()
if err != nil {
return []string{}, err
}
for _, host := range hosts {
- cmd, err := getK8sCmd(host)
+ cmd, err := getPsCmdOutput(host, service)
if err != nil {
return []string{}, err
}
if len(cmd) > 0 {
- i := bytes.Index(cmd, []byte(k8sProcess))
+ i := bytes.Index(cmd, []byte(process.String()))
if i == -1 {
- return []string{}, errors.New("missing " + k8sProcess + " command")
+ return []string{}, fmt.Errorf("missing %s command", process)
}
- return btos(cmd[i+len(k8sProcess):]), nil
+ return btos(cmd[i+len(process.String()):]), nil
}
}
return []string{}, nil
@@ -58,17 +68,17 @@ func listHosts() ([]string, error) {
return btos(out), nil
}
-// getK8sCmd returns running Kubernetes API server command with its parameters.
+// getPsCmdOutput returns running Kubernetes service command with its parameters.
// It queries default environment set in configuration file.
-func getK8sCmd(host string) ([]byte, error) {
+func getPsCmdOutput(host string, service check.Service) ([]byte, error) {
// Following is equivalent to:
// $ rancher --host $HOST \
// docker ps --no-trunc \
- // --filter "label=io.rancher.stack_service.name=kubernetes/kubernetes" \
+ // --filter "label=io.rancher.stack_service.name=$SERVICE" \
// --format "{{.Command}}"
cmd := exec.Command(bin, paramHost, host,
cmdDocker, cmdDockerCmdPs, cmdDockerCmdPsParams,
- cmdDockerCmdPsFilter, cmdDockerCmdPsFilterArgs,
+ cmdDockerCmdPsFilter, cmdDockerCmdPsFilterArgs+service.String(),
cmdDockerCmdPsFormat, cmdDockerCmdPsFormatArgs)
out, err := cmd.Output()
if err != nil {
diff --git a/test/security/k8s/src/check/raw/raw.go b/test/security/k8s/src/check/raw/raw.go
index 4efa1d4f8..2a9f0a17f 100644
--- a/test/security/k8s/src/check/raw/raw.go
+++ b/test/security/k8s/src/check/raw/raw.go
@@ -3,7 +3,7 @@ package raw
import (
"bytes"
- "errors"
+ "fmt"
"io/ioutil"
"os/user"
"path/filepath"
@@ -11,6 +11,7 @@ import (
"golang.org/x/crypto/ssh"
kh "golang.org/x/crypto/ssh/knownhosts"
+ "check"
"check/config"
)
@@ -19,15 +20,21 @@ const (
etcd = "etcd"
worker = "worker"
- k8sProcess = "kube-apiserver"
- dockerInspectCmd = "docker inspect " + k8sProcess + " --format {{.Args}}"
-
knownHostsFile = "~/.ssh/known_hosts"
)
-// GetK8sParams returns parameters of running Kubernetes API servers.
+// Raw implements Informer interface.
+type Raw struct {
+ check.Informer
+}
+
+// GetAPIParams returns parameters of running Kubernetes API servers.
// It queries only cluster nodes with "controlplane" role.
-func GetK8sParams() ([]string, error) {
+func (r *Raw) GetAPIParams() ([]string, error) {
+ return getProcessParams(check.APIProcess)
+}
+
+func getProcessParams(process check.Command) ([]string, error) {
nodes, err := config.GetNodesInfo()
if err != nil {
return []string{}, err
@@ -35,17 +42,17 @@ func GetK8sParams() ([]string, error) {
for _, node := range nodes {
if isControlplaneNode(node.Role) {
- cmd, err := getK8sCmd(node)
+ cmd, err := getInspectCmdOutput(node, process)
if err != nil {
return []string{}, err
}
if len(cmd) > 0 {
- i := bytes.Index(cmd, []byte(k8sProcess))
+ i := bytes.Index(cmd, []byte(process.String()))
if i == -1 {
- return []string{}, errors.New("missing " + k8sProcess + " command")
+ return []string{}, fmt.Errorf("missing %s command", process)
}
- return btos(cmd[i+len(k8sProcess):]), nil
+ return btos(cmd[i+len(process.String()):]), nil
}
}
}
@@ -62,7 +69,7 @@ func isControlplaneNode(roles []string) bool {
return false
}
-func getK8sCmd(node config.NodeInfo) ([]byte, error) {
+func getInspectCmdOutput(node config.NodeInfo, cmd check.Command) ([]byte, error) {
path, err := expandPath(node.SSHKeyPath)
if err != nil {
return nil, err
@@ -95,7 +102,7 @@ func getK8sCmd(node config.NodeInfo) ([]byte, error) {
}
defer conn.Close()
- out, err := runCommand(dockerInspectCmd, conn)
+ out, err := runCommand(fmt.Sprintf("docker inspect %s --format {{.Args}}", cmd), conn)
if err != nil {
return nil, err
}
diff --git a/test/security/k8s/src/check/validators/master/master.go b/test/security/k8s/src/check/validators/master/master.go
index e9dc66cc6..ff3b79648 100644
--- a/test/security/k8s/src/check/validators/master/master.go
+++ b/test/security/k8s/src/check/validators/master/master.go
@@ -6,52 +6,53 @@ import (
"check/validators/master/api"
)
-// Check validates master node complies with CIS guideliness.
-func Check(k8sParams []string) {
- log.Printf("IsBasicAuthFileAbsent: %t\n", api.IsBasicAuthFileAbsent(k8sParams))
- log.Printf("IsTokenAuthFileAbsent: %t\n", api.IsTokenAuthFileAbsent(k8sParams))
- log.Printf("IsInsecureAllowAnyTokenAbsent: %t\n", api.IsInsecureAllowAnyTokenAbsent(k8sParams))
-
- log.Printf("IsAnonymousAuthDisabled: %t\n", api.IsAnonymousAuthDisabled(k8sParams))
- log.Printf("IsInsecurePortUnbound: %t\n", api.IsInsecurePortUnbound(k8sParams))
- log.Printf("IsProfilingDisabled: %t\n", api.IsProfilingDisabled(k8sParams))
- log.Printf("IsRepairMalformedUpdatesDisabled: %t\n", api.IsRepairMalformedUpdatesDisabled(k8sParams))
- log.Printf("IsServiceAccountLookupEnabled: %t\n", api.IsServiceAccountLookupEnabled(k8sParams))
-
- log.Printf("IsKubeletHTTPSAbsentOrEnabled: %t\n", api.IsKubeletHTTPSAbsentOrEnabled(k8sParams))
- log.Printf("IsInsecureBindAddressAbsentOrLoopback: %t\n", api.IsInsecureBindAddressAbsentOrLoopback(k8sParams))
- log.Printf("IsSecurePortAbsentOrValid: %t\n", api.IsSecurePortAbsentOrValid(k8sParams))
-
- log.Printf("IsAlwaysAdmitAdmissionControlPluginExcluded: %t\n", api.IsAlwaysAdmitAdmissionControlPluginExcluded(k8sParams))
-
- log.Printf("IsAlwaysPullImagesAdmissionControlPluginIncluded: %t\n", api.IsAlwaysPullImagesAdmissionControlPluginIncluded(k8sParams))
- log.Printf("IsDenyEscalatingExecAdmissionControlPluginIncluded: %t\n", api.IsDenyEscalatingExecAdmissionControlPluginIncluded(k8sParams))
- log.Printf("IsSecurityContextDenyAdmissionControlPluginIncluded: %t\n", api.IsSecurityContextDenyAdmissionControlPluginIncluded(k8sParams))
- log.Printf("IsPodSecurityPolicyAdmissionControlPluginIncluded: %t\n", api.IsPodSecurityPolicyAdmissionControlPluginIncluded(k8sParams))
- log.Printf("IsServiceAccountAdmissionControlPluginIncluded: %t\n", api.IsServiceAccountAdmissionControlPluginIncluded(k8sParams))
- log.Printf("IsNodeRestrictionAdmissionControlPluginIncluded: %t\n", api.IsNodeRestrictionAdmissionControlPluginIncluded(k8sParams))
- log.Printf("IsEventRateLimitAdmissionControlPluginIncluded: %t\n", api.IsEventRateLimitAdmissionControlPluginIncluded(k8sParams))
-
- log.Printf("IsNamespaceLifecycleAdmissionControlPluginNotExcluded: %t\n", api.IsNamespaceLifecycleAdmissionControlPluginNotExcluded(k8sParams))
-
- log.Printf("IsAlwaysAllowAuthorizationModeExcluded: %t\n", api.IsAlwaysAllowAuthorizationModeExcluded(k8sParams))
- log.Printf("IsNodeAuthorizationModeIncluded: %t\n", api.IsNodeAuthorizationModeIncluded(k8sParams))
-
- log.Printf("IsAuditLogPathSet: %t\n", api.IsAuditLogPathSet(k8sParams))
- log.Printf("IsAuditLogMaxAgeValid: %t\n", api.IsAuditLogMaxAgeValid(k8sParams))
- log.Printf("IsAuditLogMaxBackupValid: %t\n", api.IsAuditLogMaxBackupValid(k8sParams))
- log.Printf("IsAuditLogMaxSizeValid: %t\n", api.IsAuditLogMaxSizeValid(k8sParams))
-
- log.Printf("IsRequestTimeoutValid: %t\n", api.IsRequestTimeoutValid(k8sParams))
-
- log.Printf("IsKubeletCertificateAuthoritySet: %t\n", api.IsKubeletCertificateAuthoritySet(k8sParams))
- log.Printf("IsClientCertificateAuthoritySet: %t\n", api.IsClientCertificateAuthoritySet(k8sParams))
- log.Printf("IsEtcdCertificateAuthoritySet: %t\n", api.IsEtcdCertificateAuthoritySet(k8sParams))
-
- log.Printf("IsServiceAccountKeySet: %t\n", api.IsServiceAccountKeySet(k8sParams))
- log.Printf("IsKubeletClientCertificateAndKeySet: %t\n", api.IsKubeletClientCertificateAndKeySet(k8sParams))
- log.Printf("IsEtcdCertificateAndKeySet: %t\n", api.IsEtcdCertificateAndKeySet(k8sParams))
- log.Printf("IsTLSCertificateAndKeySet: %t\n", api.IsTLSCertificateAndKeySet(k8sParams))
-
- log.Printf("IsStrongCryptoCipherInUse: %t\n", api.IsStrongCryptoCipherInUse(k8sParams))
+// CheckAPI validates API server complies with CIS guideliness.
+func CheckAPI(params []string) {
+ log.Println("==> API:")
+ log.Printf("IsBasicAuthFileAbsent: %t\n", api.IsBasicAuthFileAbsent(params))
+ log.Printf("IsTokenAuthFileAbsent: %t\n", api.IsTokenAuthFileAbsent(params))
+ log.Printf("IsInsecureAllowAnyTokenAbsent: %t\n", api.IsInsecureAllowAnyTokenAbsent(params))
+
+ log.Printf("IsAnonymousAuthDisabled: %t\n", api.IsAnonymousAuthDisabled(params))
+ log.Printf("IsInsecurePortUnbound: %t\n", api.IsInsecurePortUnbound(params))
+ log.Printf("IsProfilingDisabled: %t\n", api.IsProfilingDisabled(params))
+ log.Printf("IsRepairMalformedUpdatesDisabled: %t\n", api.IsRepairMalformedUpdatesDisabled(params))
+ log.Printf("IsServiceAccountLookupEnabled: %t\n", api.IsServiceAccountLookupEnabled(params))
+
+ log.Printf("IsKubeletHTTPSAbsentOrEnabled: %t\n", api.IsKubeletHTTPSAbsentOrEnabled(params))
+ log.Printf("IsInsecureBindAddressAbsentOrLoopback: %t\n", api.IsInsecureBindAddressAbsentOrLoopback(params))
+ log.Printf("IsSecurePortAbsentOrValid: %t\n", api.IsSecurePortAbsentOrValid(params))
+
+ log.Printf("IsAlwaysAdmitAdmissionControlPluginExcluded: %t\n", api.IsAlwaysAdmitAdmissionControlPluginExcluded(params))
+
+ log.Printf("IsAlwaysPullImagesAdmissionControlPluginIncluded: %t\n", api.IsAlwaysPullImagesAdmissionControlPluginIncluded(params))
+ log.Printf("IsDenyEscalatingExecAdmissionControlPluginIncluded: %t\n", api.IsDenyEscalatingExecAdmissionControlPluginIncluded(params))
+ log.Printf("IsSecurityContextDenyAdmissionControlPluginIncluded: %t\n", api.IsSecurityContextDenyAdmissionControlPluginIncluded(params))
+ log.Printf("IsPodSecurityPolicyAdmissionControlPluginIncluded: %t\n", api.IsPodSecurityPolicyAdmissionControlPluginIncluded(params))
+ log.Printf("IsServiceAccountAdmissionControlPluginIncluded: %t\n", api.IsServiceAccountAdmissionControlPluginIncluded(params))
+ log.Printf("IsNodeRestrictionAdmissionControlPluginIncluded: %t\n", api.IsNodeRestrictionAdmissionControlPluginIncluded(params))
+ log.Printf("IsEventRateLimitAdmissionControlPluginIncluded: %t\n", api.IsEventRateLimitAdmissionControlPluginIncluded(params))
+
+ log.Printf("IsNamespaceLifecycleAdmissionControlPluginNotExcluded: %t\n", api.IsNamespaceLifecycleAdmissionControlPluginNotExcluded(params))
+
+ log.Printf("IsAlwaysAllowAuthorizationModeExcluded: %t\n", api.IsAlwaysAllowAuthorizationModeExcluded(params))
+ log.Printf("IsNodeAuthorizationModeIncluded: %t\n", api.IsNodeAuthorizationModeIncluded(params))
+
+ log.Printf("IsAuditLogPathSet: %t\n", api.IsAuditLogPathSet(params))
+ log.Printf("IsAuditLogMaxAgeValid: %t\n", api.IsAuditLogMaxAgeValid(params))
+ log.Printf("IsAuditLogMaxBackupValid: %t\n", api.IsAuditLogMaxBackupValid(params))
+ log.Printf("IsAuditLogMaxSizeValid: %t\n", api.IsAuditLogMaxSizeValid(params))
+
+ log.Printf("IsRequestTimeoutValid: %t\n", api.IsRequestTimeoutValid(params))
+
+ log.Printf("IsKubeletCertificateAuthoritySet: %t\n", api.IsKubeletCertificateAuthoritySet(params))
+ log.Printf("IsClientCertificateAuthoritySet: %t\n", api.IsClientCertificateAuthoritySet(params))
+ log.Printf("IsEtcdCertificateAuthoritySet: %t\n", api.IsEtcdCertificateAuthoritySet(params))
+
+ log.Printf("IsServiceAccountKeySet: %t\n", api.IsServiceAccountKeySet(params))
+ log.Printf("IsKubeletClientCertificateAndKeySet: %t\n", api.IsKubeletClientCertificateAndKeySet(params))
+ log.Printf("IsEtcdCertificateAndKeySet: %t\n", api.IsEtcdCertificateAndKeySet(params))
+ log.Printf("IsTLSCertificateAndKeySet: %t\n", api.IsTLSCertificateAndKeySet(params))
+
+ log.Printf("IsStrongCryptoCipherInUse: %t\n", api.IsStrongCryptoCipherInUse(params))
}
--
cgit 1.2.3-korg