1
0
mirror of https://github.com/aquasecurity/kube-bench.git synced 2024-11-22 16:18:07 +00:00

Merge pull request #47 from aquasecurity/auto-detect

Auto-detect executables and config files
This commit is contained in:
Liz Rice 2017-09-04 10:00:24 +01:00 committed by GitHub
commit 516343eb06
8 changed files with 493 additions and 210 deletions

View File

@ -7,106 +7,112 @@
# nodeControls: ./cfg/node.yaml # nodeControls: ./cfg/node.yaml
# federatedControls: ./cfg/federated.yaml # federatedControls: ./cfg/federated.yaml
## Support components master:
etcd: components:
bin: etcd - apiserver
conf: /etc/etcd/etcd.conf - scheduler
- controllermanager
- etcd
- flanneld
# kubernetes is a component to cover the config file /etc/kubernetes/config that is referred to in the benchmark
- kubernetes
flanneld: kubernetes:
bin: flanneld defaultconf: /etc/kubernetes/config
conf: /etc/sysconfig/flanneld
# Installation apiserver:
# Configure kubernetes component binaries and paths to their configuration files. bins:
installation: - "kube-apiserver"
default: - "hyperkube apiserver"
config: /etc/kubernetes/config - "apiserver"
master: confs:
bin: - /etc/kubernetes/manifests/kube-apiserver.yaml
apiserver: apiserver - /etc/kubernetes/apiserver.conf
scheduler: scheduler - /etc/kubernetes/apiserver
controller-manager: controller-manager defaultconf: /etc/kubernetes/apiserver
conf:
apiserver: /etc/kubernetes/apiserver
scheduler: /etc/kubernetes/scheduler
controller-manager: /etc/kubernetes/controller-manager
node:
bin:
kubelet: kubelet
proxy: proxy
conf:
kubelet: /etc/kubernetes/kubelet
proxy: /etc/kubernetes/proxy
federated:
bin:
apiserver: federation-apiserver
controller-manager: federation-controller-manager
kops: scheduler:
config: /etc/kubernetes/config bins:
master: - "kube-scheduler"
bin: - "hyperkube scheduler"
apiserver: apiserver - "scheduler"
scheduler: scheduler confs:
controller-manager: controller-manager - /etc/kubernetes/manifests/kube-scheduler.yaml
conf: - /etc/kubernetes/scheduler.conf
apiserver: /etc/kubernetes/apiserver - /etc/kubernetes/scheduler
scheduler: /etc/kubernetes/scheduler defaultconf: /etc/kubernetes/scheduler
controller-manager: /etc/kubernetes/apiserver
node: controllermanager:
bin: bins:
kubelet: kubelet - "kube-controller-manager"
proxy: proxy - "hyperkube controller-manager"
conf: - "controller-manager"
kubelet: /etc/kubernetes/kubelet confs:
proxy: /etc/kubernetes/proxy - /etc/kubernetes/manifests/kube-controller-manager.yaml
federated: - /etc/kubernetes/controller-manager.conf
bin: - /etc/kubernetes/controller-manager
apiserver: federation-apiserver defaultconf: /etc/kubernetes/controller-manager
controller-manager: federation-controller-manager
etcd:
optional: true
bins:
- "etcd"
confs:
- /etc/kubernetes/manifests/etcd.yaml
- /etc/etcd/etcd.conf
defaultconf: /etc/etcd/etcd.conf
flanneld:
optional: true
bins:
- flanneld
defaultconf: /etc/sysconfig/flanneld
node:
components:
- kubelet
- proxy
# kubernetes is a component to cover the config file /etc/kubernetes/config that is referred to in the benchmark
- kubernetes
kubernetes:
defaultconf: /etc/kubernetes/config
kubelet:
bins:
- "hyperkube kubelet"
- "kubelet"
confs:
- /etc/kubernetes/kubelet.conf
- /etc/kubernetes/kubelet
defaultconf: "/etc/kubernetes/kubelet.conf"
proxy:
bins:
- "kube-proxy"
- "hyperkube proxy"
- "proxy"
confs:
- /etc/kubernetes/proxy.conf
- /etc/kubernetes/proxy
- /etc/kubernetes/addons/kube-proxy-daemonset.yaml
federated:
components:
- fedapiserver
- fedcontrollermanager
fedapiserver:
bins:
- "hyperkube federation-apiserver"
- "kube-federation-apiserver"
- "federation-apiserver"
fedcontrollermanager:
bins:
- "hyperkube federation-controller-manager"
- "kube-federation-controller-manager"
- "federation-controller-manager"
hyperkube:
config: /etc/kubernetes/config
master:
bin:
apiserver: hyperkube apiserver
scheduler: hyperkube scheduler
controller-manager: hyperkube controller-manager
conf:
apiserver: /etc/kubernetes/manifests/kube-apiserver.yaml
scheduler: /etc/kubernetes/manifests/kube-scheduler.yaml
controller-manager: /etc/kubernetes/manifests/kube-controller-manager.yaml
node:
bin:
kubelet: hyperkube kubelet
proxy: hyperkube proxy
conf:
kubelet: /etc/kubernetes/kubelet
proxy: /etc/kubernetes/addons/kube-proxy-daemonset.yaml
federated:
bin:
apiserver: hyperkube federation-apiserver
controller-manager: hyperkube federation-controller-manager
kubeadm:
config: /etc/kubernetes/config
master:
bin:
apiserver: kube-apiserver
scheduler: kube-scheduler
controller-manager: kube-controller-manager
conf:
apiserver: /etc/kubernetes/admin.conf
scheduler: /etc/kubernetes/scheduler.conf
controller-manager: /etc/kubernetes/controller-manager.conf
node:
bin:
kubelet: kubelet
proxy: kube-proxy
conf:
kubelet: /etc/kubernetes/kubelet.conf
proxy: /etc/kubernetes/proxy.conf
federated:
bin:
apiserver: kube-federation-apiserver
controller-manager: kube-federation-controller-manager

View File

@ -636,7 +636,7 @@ groups:
- id: 1.4.3 - id: 1.4.3
text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)" text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)"
audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; fi'" audit: "/bin/sh -c 'if test -e $kubernetesconf; then stat -c %a $kubernetesconf; fi'"
tests: tests:
bin_op: or bin_op: or
test_items: test_items:
@ -656,12 +656,12 @@ groups:
value: "600" value: "600"
set: true set: true
remediation: "Run the below command (based on the file location on your system) on the master node. remediation: "Run the below command (based on the file location on your system) on the master node.
\nFor example, chmod 644 $config" \nFor example, chmod 644 $kubernetesconf"
scored: true scored: true
- id: 1.4.4 - id: 1.4.4
text: "Ensure that the config file ownership is set to root:root (Scored)" text: "Ensure that the config file ownership is set to root:root (Scored)"
audit: "/bin/sh -c 'if test -e $config; then stat -c %U:%G $config; fi'" audit: "/bin/sh -c 'if test -e $kubernetesconf; then stat -c %U:%G $kubernetesconf; fi'"
tests: tests:
test_items: test_items:
- flag: "root:root" - flag: "root:root"
@ -670,7 +670,7 @@ groups:
value: "root:root" value: "root:root"
set: true set: true
remediation: "Run the below command (based on the file location on your system) on the master node. remediation: "Run the below command (based on the file location on your system) on the master node.
\nFor example, chown root:root $config" \nFor example, chown root:root $kubernetesconf"
scored: true scored: true
- id: 1.4.5 - id: 1.4.5

View File

@ -17,7 +17,7 @@ groups:
op: eq op: eq
value: false value: false
set: true set: true
remediation: "Edit the $config file on each node and set the KUBE_ALLOW_PRIV remediation: "Edit the $kubeletconf file on each node and set the KUBE_ALLOW_PRIV
parameter to \"--allow-privileged=false\"" parameter to \"--allow-privileged=false\""
scored: true scored: true
@ -199,7 +199,7 @@ groups:
op: eq op: eq
value: true value: true
set: true set: true
remediation: "Edit the /etc/kubernetes/kubelet file on each node and set the KUBELET_ARGS parameter remediation: "Edit the $kubeletconf file on each node and set the KUBELET_ARGS parameter
to a value to include \"--feature-gates=RotateKubeletClientCertificate=true\"." to a value to include \"--feature-gates=RotateKubeletClientCertificate=true\"."
scored: true scored: true
@ -213,7 +213,7 @@ groups:
op: eq op: eq
value: true value: true
set: true set: true
remediation: "Edit the /etc/kubernetes/kubelet file on each node and set the KUBELET_ARGS parameter remediation: "Edit the $kubeletconf file on each node and set the KUBELET_ARGS parameter
to a value to include \"--feature-gates=RotateKubeletServerCertificate=true\"." to a value to include \"--feature-gates=RotateKubeletServerCertificate=true\"."
scored: true scored: true
@ -222,7 +222,7 @@ groups:
checks: checks:
- id: 2.2.1 - id: 2.2.1
text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)" text: "Ensure that the config file permissions are set to 644 or more restrictive (Scored)"
audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; fi'" audit: "/bin/sh -c 'if test -e $kubernetesconf; then stat -c %a $kubernetesconf; fi'"
tests: tests:
bin_op: or bin_op: or
test_items: test_items:
@ -242,12 +242,12 @@ groups:
value: "600" value: "600"
set: true set: true
remediation: "Run the below command (based on the file location on your system) on the each worker node. remediation: "Run the below command (based on the file location on your system) on the each worker node.
\nFor example, chmod 644 $config" \nFor example, chmod 644 $kubernetesconf"
scored: true scored: true
- id: 2.2.2 - id: 2.2.2
text: "Ensure that the config file ownership is set to root:root (Scored)" text: "Ensure that the config file ownership is set to root:root (Scored)"
audit: "/bin/sh -c 'if test -e $config; then stat -c %U:%G $config; fi'" audit: "/bin/sh -c 'if test -e $kubernetesconf; then stat -c %U:%G $kubernetesconf; fi'"
tests: tests:
test_items: test_items:
- flag: "root:root" - flag: "root:root"
@ -256,7 +256,7 @@ groups:
value: root:root value: root:root
set: true set: true
remediation: "Run the below command (based on the file location on your system) on the each worker node. remediation: "Run the below command (based on the file location on your system) on the each worker node.
\nFor example, chown root:root $config" \nFor example, chown root:root $kubernetesconf"
scored: true scored: true
- id: 2.2.3 - id: 2.2.3

View File

@ -156,7 +156,9 @@ func (c *Check) Run() {
i++ i++
} }
glog.V(2).Info("%s\n", errmsgs) if errmsgs != "" {
glog.V(2).Info(errmsgs)
}
res := c.Tests.execute(out.String()) res := c.Tests.execute(out.String())
if res { if res {

View File

@ -17,9 +17,9 @@ package cmd
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"github.com/aquasecurity/kube-bench/check" "github.com/aquasecurity/kube-bench/check"
"github.com/golang/glog"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -52,34 +52,30 @@ var (
func runChecks(t check.NodeType) { func runChecks(t check.NodeType) {
var summary check.Summary var summary check.Summary
var file string var file string
var err error
var typeConf *viper.Viper
// Master variables glog.V(1).Info(fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
apiserverBin = viper.GetString("installation." + installation + ".master.bin.apiserver")
apiserverConf = viper.GetString("installation." + installation + ".master.conf.apiserver")
schedulerBin = viper.GetString("installation." + installation + ".master.bin.scheduler")
schedulerConf = viper.GetString("installation." + installation + ".master.conf.scheduler")
controllerManagerBin = viper.GetString("installation." + installation + ".master.bin.controller-manager")
controllerManagerConf = viper.GetString("installation." + installation + ".master.conf.controller-manager")
config = viper.GetString("installation." + installation + ".config")
etcdBin = viper.GetString("etcd.bin") switch t {
etcdConf = viper.GetString("etcd.conf") case check.MASTER:
flanneldBin = viper.GetString("flanneld.bin") file = masterFile
flanneldConf = viper.GetString("flanneld.conf") typeConf = viper.Sub("master")
case check.NODE:
file = nodeFile
typeConf = viper.Sub("node")
case check.FEDERATED:
file = federatedFile
typeConf = viper.Sub("federated")
}
// Node variables // Get the set of exectuables and config files we care about on this type of node. This also
kubeletBin = viper.GetString("installation." + installation + ".node.bin.kubelet") // checks that the executables we need for the node type are running.
kubeletConf = viper.GetString("installation." + installation + ".node.conf.kubelet") binmap := getBinaries(typeConf)
proxyBin = viper.GetString("installation." + installation + ".node.bin.proxy") confmap := getConfigFiles(typeConf)
proxyConf = viper.GetString("installation." + installation + ".node.conf.proxy")
// Federated
fedApiserverBin = viper.GetString("installation." + installation + ".federated.bin.apiserver")
fedControllerManagerBin = viper.GetString("installation." + installation + ".federated.bin.controller-manager")
// Run kubernetes installation validation checks. // Run kubernetes installation validation checks.
verifyKubeVersion(kubeMajorVersion, kubeMinorVersion) verifyKubeVersion(kubeMajorVersion, kubeMinorVersion)
verifyNodeType(t)
switch t { switch t {
case check.MASTER: case check.MASTER:
@ -96,26 +92,9 @@ func runChecks(t check.NodeType) {
} }
// Variable substitutions. Replace all occurrences of variables in controls files. // Variable substitutions. Replace all occurrences of variables in controls files.
s := multiWordReplace(string(in), "$apiserverbin", apiserverBin) s := string(in)
s = multiWordReplace(s, "$apiserverconf", apiserverConf) s = makeSubstitutions(s, "bin", binmap)
s = multiWordReplace(s, "$schedulerbin", schedulerBin) s = makeSubstitutions(s, "conf", confmap)
s = multiWordReplace(s, "$schedulerconf", schedulerConf)
s = multiWordReplace(s, "$controllermanagerbin", controllerManagerBin)
s = multiWordReplace(s, "$controllermanagerconf", controllerManagerConf)
s = multiWordReplace(s, "$config", config)
s = multiWordReplace(s, "$etcdbin", etcdBin)
s = multiWordReplace(s, "$etcdconf", etcdConf)
s = multiWordReplace(s, "$flanneldbin", flanneldBin)
s = multiWordReplace(s, "$flanneldconf", flanneldConf)
s = multiWordReplace(s, "$kubeletbin", kubeletBin)
s = multiWordReplace(s, "$kubeletconf", kubeletConf)
s = multiWordReplace(s, "$proxybin", proxyBin)
s = multiWordReplace(s, "$proxyconf", proxyConf)
s = multiWordReplace(s, "$fedapiserverbin", fedApiserverBin)
s = multiWordReplace(s, "$fedcontrollermanagerbin", fedControllerManagerBin)
controls, err := check.NewControls(t, []byte(s)) controls, err := check.NewControls(t, []byte(s))
if err != nil { if err != nil {
@ -147,41 +126,6 @@ func runChecks(t check.NodeType) {
} }
} }
// verifyNodeType checks the executables and config files are as expected
// for the specified tests (master, node or federated).
func verifyNodeType(t check.NodeType) {
var bins []string
var confs []string
switch t {
case check.MASTER:
bins = []string{apiserverBin, schedulerBin, controllerManagerBin}
confs = []string{apiserverConf, schedulerConf, controllerManagerConf}
case check.NODE:
bins = []string{kubeletBin, proxyBin}
confs = []string{kubeletConf, proxyConf}
case check.FEDERATED:
bins = []string{fedApiserverBin, fedControllerManagerBin}
}
for _, bin := range bins {
if !verifyBin(bin, ps) {
printlnWarn(fmt.Sprintf("%s is not running", bin))
}
}
for _, conf := range confs {
_, err := os.Stat(conf)
if err != nil {
if os.IsNotExist(err) {
printlnWarn(fmt.Sprintf("Missing kubernetes config file: %s", conf))
} else {
exitWithError(fmt.Errorf("error looking for file %s: %v", conf, err))
}
}
}
}
// colorPrint outputs the state in a specific colour, along with a message string // colorPrint outputs the state in a specific colour, along with a message string
func colorPrint(state check.State, s string) { func colorPrint(state check.State, s string) {
colors[state].Printf("[%s] ", state) colors[state].Printf("[%s] ", state)
@ -190,8 +134,6 @@ func colorPrint(state check.State, s string) {
// prettyPrint outputs the results to stdout in human-readable format // prettyPrint outputs the results to stdout in human-readable format
func prettyPrint(r *check.Controls, summary check.Summary) { func prettyPrint(r *check.Controls, summary check.Summary) {
colorPrint(check.INFO, fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", r.ID, r.Text)) colorPrint(check.INFO, fmt.Sprintf("%s %s\n", r.ID, r.Text))
for _, g := range r.Groups { for _, g := range r.Groups {
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Text)) colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Text))

View File

@ -34,14 +34,6 @@ var (
masterFile string masterFile string
nodeFile string nodeFile string
federatedFile string federatedFile string
loud bool
kubeConfDir string
etcdConfDir string
flanneldConfDir string
installation string
) )
// RootCmd represents the base command when called without any subcommands // RootCmd represents the base command when called without any subcommands
@ -67,12 +59,6 @@ func init() {
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON") RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
RootCmd.PersistentFlags().StringVar(
&installation,
"installation",
"default",
"Specify how kubernetes cluster was installed. Possible values are default,hyperkube,kops,kubeadm",
)
RootCmd.PersistentFlags().StringVarP( RootCmd.PersistentFlags().StringVarP(
&checkList, &checkList,
"check", "check",

View File

@ -10,6 +10,7 @@ import (
"github.com/aquasecurity/kube-bench/check" "github.com/aquasecurity/kube-bench/check"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/viper"
) )
var ( var (
@ -22,6 +23,14 @@ var (
} }
) )
var psFunc func(string) string
var statFunc func(string) (os.FileInfo, error)
func init() {
psFunc = ps
statFunc = os.Stat
}
func printlnWarn(msg string) { func printlnWarn(msg string) {
fmt.Fprintf(os.Stderr, "[%s] %s\n", fmt.Fprintf(os.Stderr, "[%s] %s\n",
colors[check.WARN].Sprintf("%s", check.WARN), colors[check.WARN].Sprintf("%s", check.WARN),
@ -43,7 +52,7 @@ func exitWithError(err error) {
func continueWithError(err error, msg string) string { func continueWithError(err error, msg string) string {
if err != nil { if err != nil {
glog.V(1).Info(err) glog.V(2).Info(err)
} }
if msg != "" { if msg != "" {
@ -75,8 +84,71 @@ func ps(proc string) string {
return string(out) return string(out)
} }
// getBinaries finds which of the set of candidate executables are running
func getBinaries(v *viper.Viper) map[string]string {
binmap := make(map[string]string)
for _, component := range v.GetStringSlice("components") {
s := v.Sub(component)
if s == nil {
continue
}
optional := s.GetBool("optional")
bins := s.GetStringSlice("bins")
if len(bins) > 0 {
bin, err := findExecutable(bins)
if err != nil && !optional {
exitWithError(fmt.Errorf("need %s executable but none of the candidates are running", component))
}
// Default the executable name that we'll substitute to the name of the component
if bin == "" {
bin = component
glog.V(2).Info(fmt.Sprintf("Component %s not running", component))
} else {
glog.V(2).Info(fmt.Sprintf("Component %s uses running binary %s", component, bin))
}
binmap[component] = bin
}
}
return binmap
}
// getConfigFiles finds which of the set of candidate config files exist
func getConfigFiles(v *viper.Viper) map[string]string {
confmap := make(map[string]string)
for _, component := range v.GetStringSlice("components") {
s := v.Sub(component)
if s == nil {
continue
}
// See if any of the candidate config files exist
conf := findConfigFile(s.GetStringSlice("confs"))
if conf == "" {
if s.IsSet("defaultconf") {
conf = s.GetString("defaultconf")
glog.V(2).Info(fmt.Sprintf("Using default config file name '%s' for component %s", conf, component))
} else {
// Default the config file name that we'll substitute to the name of the component
printlnWarn(fmt.Sprintf("Missing config file for %s", component))
conf = component
}
} else {
glog.V(2).Info(fmt.Sprintf("Component %s uses config file '%s'", component, conf))
}
confmap[component] = conf
}
return confmap
}
// verifyBin checks that the binary specified is running // verifyBin checks that the binary specified is running
func verifyBin(bin string, psFunc func(string) string) bool { func verifyBin(bin string) bool {
// Strip any quotes // Strip any quotes
bin = strings.Trim(bin, "'\"") bin = strings.Trim(bin, "'\"")
@ -87,7 +159,47 @@ func verifyBin(bin string, psFunc func(string) string) bool {
proc := strings.Fields(bin)[0] proc := strings.Fields(bin)[0]
out := psFunc(proc) out := psFunc(proc)
return strings.Contains(out, bin) // There could be multiple lines in the ps output
// The binary needs to be the first word in the ps output, except that it could be preceded by a path
// e.g. /usr/bin/kubelet is a match for kubelet
// but apiserver is not a match for kube-apiserver
reFirstWord := regexp.MustCompile(`^(\S*\/)*` + bin)
lines := strings.Split(out, "\n")
for _, l := range lines {
if reFirstWord.Match([]byte(l)) {
return true
}
}
return false
}
// fundConfigFile looks through a list of possible config files and finds the first one that exists
func findConfigFile(candidates []string) string {
for _, c := range candidates {
_, err := statFunc(c)
if err == nil {
return c
}
if !os.IsNotExist(err) {
exitWithError(fmt.Errorf("error looking for file %s: %v", c, err))
}
}
return ""
}
// findExecutable looks through a list of possible executable names and finds the first one that's running
func findExecutable(candidates []string) (string, error) {
for _, c := range candidates {
if verifyBin(c) {
return c, nil
} else {
glog.V(1).Info(fmt.Sprintf("executable '%s' not running", c))
}
}
return "", fmt.Errorf("no candidates running")
} }
func verifyKubeVersion(major string, minor string) { func verifyKubeVersion(major string, minor string) {
@ -159,3 +271,17 @@ func multiWordReplace(s string, subname string, sub string) string {
return strings.Replace(s, subname, sub, -1) return strings.Replace(s, subname, sub, -1)
} }
func makeSubstitutions(s string, ext string, m map[string]string) string {
for k, v := range m {
subst := "$" + k + ext
if v == "" {
glog.V(2).Info(fmt.Sprintf("No subsitution for '%s'\n", subst))
continue
}
glog.V(1).Info(fmt.Sprintf("Substituting %s with '%s'\n", subst, v))
s = multiWordReplace(s, subst, v)
}
return s
}

View File

@ -15,9 +15,13 @@
package cmd package cmd
import ( import (
"os"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"testing" "testing"
"github.com/spf13/viper"
) )
func TestCheckVersion(t *testing.T) { func TestCheckVersion(t *testing.T) {
@ -78,10 +82,19 @@ func TestVersionMatch(t *testing.T) {
} }
var g string var g string
var e []error
var eIndex int
func fakeps(proc string) string { func fakeps(proc string) string {
return g return g
} }
func fakestat(file string) (os.FileInfo, error) {
err := e[eIndex]
eIndex++
return nil, err
}
func TestVerifyBin(t *testing.T) { func TestVerifyBin(t *testing.T) {
cases := []struct { cases := []struct {
proc string proc string
@ -95,14 +108,110 @@ func TestVerifyBin(t *testing.T) {
{proc: "cmd", psOut: "cmd param1 param2", exp: true}, {proc: "cmd", psOut: "cmd param1 param2", exp: true},
{proc: "cmd param", psOut: "cmd param1 param2", exp: true}, {proc: "cmd param", psOut: "cmd param1 param2", exp: true},
{proc: "cmd param", psOut: "cmd", exp: false}, {proc: "cmd param", psOut: "cmd", exp: false},
{proc: "cmd", psOut: "cmd x \ncmd y", exp: true},
{proc: "cmd y", psOut: "cmd x \ncmd y", exp: true},
{proc: "cmd", psOut: "/usr/bin/cmd", exp: true},
{proc: "cmd", psOut: "kube-cmd", exp: false},
{proc: "cmd", psOut: "/usr/bin/kube-cmd", exp: false},
} }
psFunc = fakeps
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
g = c.psOut
v := verifyBin(c.proc)
if v != c.exp {
t.Fatalf("Expected %v got %v", c.exp, v)
}
})
}
}
func TestFindExecutable(t *testing.T) {
cases := []struct {
candidates []string // list of executables we'd consider
psOut string // fake output from ps
exp string // the one we expect to find in the (fake) ps output
expErr bool
}{
{candidates: []string{"one", "two", "three"}, psOut: "two", exp: "two"},
{candidates: []string{"one", "two", "three"}, psOut: "two three", exp: "two"},
{candidates: []string{"one double", "two double", "three double"}, psOut: "two double is running", exp: "two double"},
{candidates: []string{"one", "two", "three"}, psOut: "blah", expErr: true},
{candidates: []string{"one double", "two double", "three double"}, psOut: "two", expErr: true},
{candidates: []string{"apiserver", "kube-apiserver"}, psOut: "kube-apiserver", exp: "kube-apiserver"},
{candidates: []string{"apiserver", "kube-apiserver", "hyperkube-apiserver"}, psOut: "kube-apiserver", exp: "kube-apiserver"},
}
psFunc = fakeps
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
g = c.psOut
e, err := findExecutable(c.candidates)
if e != c.exp {
t.Fatalf("Expected %v got %v", c.exp, e)
}
if err == nil && c.expErr {
t.Fatalf("Expected error")
}
if err != nil && !c.expErr {
t.Fatalf("Didn't expect error: %v", err)
}
})
}
}
func TestGetBinaries(t *testing.T) {
cases := []struct {
config map[string]interface{}
psOut string
exp map[string]string
}{
{
config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}},
psOut: "kube-apiserver",
exp: map[string]string{"apiserver": "kube-apiserver"},
},
{
// "thing" is not in the list of components
config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
psOut: "kube-apiserver thing",
exp: map[string]string{"apiserver": "kube-apiserver"},
},
{
// "anotherthing" in list of components but doesn't have a defintion
config: map[string]interface{}{"components": []string{"apiserver", "anotherthing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
psOut: "kube-apiserver thing",
exp: map[string]string{"apiserver": "kube-apiserver"},
},
{
// more than one component
config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}}},
psOut: "kube-apiserver \nthing",
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
},
{
// default binary to component name
config: map[string]interface{}{"components": []string{"apiserver", "thing"}, "apiserver": map[string]interface{}{"bins": []string{"apiserver", "kube-apiserver"}}, "thing": map[string]interface{}{"bins": []string{"something else", "thing"}, "optional": true}},
psOut: "kube-apiserver \notherthing some params",
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
},
}
v := viper.New()
psFunc = fakeps
for id, c := range cases { for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) { t.Run(strconv.Itoa(id), func(t *testing.T) {
g = c.psOut g = c.psOut
v := verifyBin(c.proc, fakeps) for k, val := range c.config {
if v != c.exp { v.Set(k, val)
t.Fatalf("Expected %v got %v", c.exp, v) }
m := getBinaries(v)
if !reflect.DeepEqual(m, c.exp) {
t.Fatalf("Got %v\nExpected %v", m, c.exp)
} }
}) })
} }
@ -129,3 +238,115 @@ func TestMultiWordReplace(t *testing.T) {
}) })
} }
} }
func TestFindConfigFile(t *testing.T) {
cases := []struct {
input []string
statResults []error
exp string
}{
{input: []string{"myfile"}, statResults: []error{nil}, exp: "myfile"},
{input: []string{"thisfile", "thatfile"}, statResults: []error{os.ErrNotExist, nil}, exp: "thatfile"},
{input: []string{"thisfile", "thatfile"}, statResults: []error{os.ErrNotExist, os.ErrNotExist}, exp: ""},
}
statFunc = fakestat
for id, c := range cases {
t.Run(strconv.Itoa(id), func(t *testing.T) {
e = c.statResults
eIndex = 0
conf := findConfigFile(c.input)
if conf != c.exp {
t.Fatalf("Got %s expected %s", conf, c.exp)
}
})
}
}
func TestGetConfigFiles(t *testing.T) {
cases := []struct {
config map[string]interface{}
exp map[string]string
statResults []error
}{
{
config: map[string]interface{}{"components": []string{"apiserver"}, "apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}}},
statResults: []error{os.ErrNotExist, nil},
exp: map[string]string{"apiserver": "kube-apiserver"},
},
{
// Component "thing" isn't included in the list of components
config: map[string]interface{}{
"components": []string{"apiserver"},
"apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
"thing": map[string]interface{}{"confs": []string{"/my/file/thing"}}},
statResults: []error{os.ErrNotExist, nil},
exp: map[string]string{"apiserver": "kube-apiserver"},
},
{
// More than one component
config: map[string]interface{}{
"components": []string{"apiserver", "thing"},
"apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
"thing": map[string]interface{}{"confs": []string{"/my/file/thing"}}},
statResults: []error{os.ErrNotExist, nil, nil},
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "/my/file/thing"},
},
{
// Default thing to specified default config
config: map[string]interface{}{
"components": []string{"apiserver", "thing"},
"apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
"thing": map[string]interface{}{"confs": []string{"/my/file/thing"}, "defaultconf": "another/thing"}},
statResults: []error{os.ErrNotExist, nil, os.ErrNotExist},
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "another/thing"},
},
{
// Default thing to component name
config: map[string]interface{}{
"components": []string{"apiserver", "thing"},
"apiserver": map[string]interface{}{"confs": []string{"apiserver", "kube-apiserver"}},
"thing": map[string]interface{}{"confs": []string{"/my/file/thing"}}},
statResults: []error{os.ErrNotExist, nil, os.ErrNotExist},
exp: map[string]string{"apiserver": "kube-apiserver", "thing": "thing"},
},
}
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 := getConfigFiles(v)
if !reflect.DeepEqual(m, c.exp) {
t.Fatalf("Got %v\nExpected %v", m, c.exp)
}
})
}
}
func TestMakeSubsitutions(t *testing.T) {
cases := []struct {
input string
subst map[string]string
exp string
}{
{input: "Replace $thisbin", subst: map[string]string{"this": "that"}, exp: "Replace that"},
{input: "Replace $thisbin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace that"},
{input: "Replace $thisbin and $herebin", subst: map[string]string{"this": "that", "here": "there"}, exp: "Replace that and there"},
}
for _, c := range cases {
t.Run(c.input, func(t *testing.T) {
s := makeSubstitutions(c.input, "bin", c.subst)
if s != c.exp {
t.Fatalf("Got %s expected %s", s, c.exp)
}
})
}
}