diff --git a/cmd/common.go b/cmd/common.go index 69bec28..d27dc1f 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -45,7 +45,8 @@ var ( errmsgs string // TODO: Consider specifying this in config file. - kubeVersion = "1.7.0" + kubeMajorVersion = "1" + kubeMinorVersion = "7" ) func runChecks(t check.NodeType) { @@ -77,6 +78,7 @@ func runChecks(t check.NodeType) { fedControllerManagerBin = viper.GetString("installation." + installation + ".federated.bin.controller-manager") // Run kubernetes installation validation checks. + verifyKubeVersion(kubeMajorVersion, kubeMinorVersion) verifyNodeType(t) switch t { @@ -150,15 +152,12 @@ func runChecks(t check.NodeType) { func verifyNodeType(t check.NodeType) { switch t { case check.MASTER: - verifyKubeVersion(apiserverBin) verifyBin(apiserverBin, schedulerBin, controllerManagerBin) verifyConf(apiserverConf, schedulerConf, controllerManagerConf) case check.NODE: - verifyKubeVersion(kubeletBin) verifyBin(kubeletBin, proxyBin) verifyConf(kubeletConf, proxyConf) case check.FEDERATED: - verifyKubeVersion(fedApiserverBin) verifyBin(fedApiserverBin, fedControllerManagerBin) } } diff --git a/cmd/util.go b/cmd/util.go index 1885952..937e3e0 100644 --- a/cmd/util.go +++ b/cmd/util.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "os/exec" + "regexp" "strings" "github.com/aquasecurity/kube-bench/check" @@ -123,25 +124,61 @@ func verifyBin(binPath ...string) { } } -func verifyKubeVersion(b string) { +func verifyKubeVersion(major string, minor string) { // These executables might not be on the user's path. - // TODO! Check the version number using kubectl, which is more likely to be on the path. - _, err := exec.LookPath(b) + _, err := exec.LookPath("kubectl") if err != nil { continueWithError(err, sprintlnWarn("Kubernetes version check skipped")) return } - cmd := exec.Command(b, "--version") + cmd := exec.Command("kubectl", "version") out, err := cmd.Output() if err != nil { - continueWithError(err, sprintlnWarn("Kubernetes version check skipped")) + s := fmt.Sprintf("Kubernetes version check skipped with error %v", err) + continueWithError(err, sprintlnWarn(s)) return } - matched := strings.Contains(string(out), kubeVersion) - if !matched { - printlnWarn(fmt.Sprintf("Unsupported kubernetes version: %s", out)) + msg := checkVersion("Client", string(out), major, minor) + if msg != "" { + continueWithError(fmt.Errorf(msg), msg) + } + + msg = checkVersion("Server", string(out), major, minor) + if msg != "" { + continueWithError(fmt.Errorf(msg), msg) } } + +var regexVersionMajor = regexp.MustCompile("Major:\"([0-9]+)\"") +var regexVersionMinor = regexp.MustCompile("Minor:\"([0-9]+)\"") + +func checkVersion(x string, s string, expMajor string, expMinor string) string { + regexVersion, err := regexp.Compile(x + " Version: version.Info{(.*)}") + if err != nil { + return fmt.Sprintf("Error checking Kubernetes version: %v", err) + } + + ss := regexVersion.FindString(s) + major := versionMatch(regexVersionMajor, ss) + minor := versionMatch(regexVersionMinor, ss) + if major == "" || minor == "" { + return fmt.Sprintf("Couldn't find %s version from kubectl output '%s'", x, s) + } + + if major != expMajor || minor != expMinor { + return fmt.Sprintf("Unexpected %s version %s.%s", x, major, minor) + } + + return "" +} + +func versionMatch(r *regexp.Regexp, s string) string { + match := r.FindStringSubmatch(s) + if len(match) < 2 { + return "" + } + return match[1] +} diff --git a/cmd/util_test.go b/cmd/util_test.go new file mode 100644 index 0000000..74d955f --- /dev/null +++ b/cmd/util_test.go @@ -0,0 +1,77 @@ +// Copyright © 2017 Aqua Security Software Ltd. +// +// Licensed 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 cmd + +import ( + "regexp" + "testing" +) + +func TestCheckVersion(t *testing.T) { + kubeoutput := `Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-06-30T09:51:01Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"darwin/amd64"} + Server Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-07-26T00:12:31Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}` + cases := []struct { + t string + s string + major string + minor string + exp string + }{ + {t: "Client", s: kubeoutput, major: "1", minor: "7"}, + {t: "Server", s: kubeoutput, major: "1", minor: "7"}, + {t: "Client", s: kubeoutput, major: "1", minor: "6", exp: "Unexpected Client version 1.7"}, + {t: "Client", s: kubeoutput, major: "2", minor: "0", exp: "Unexpected Client version 1.7"}, + {t: "Server", s: "something unexpected", major: "2", minor: "0", exp: "Couldn't find Server version from kubectl output 'something unexpected'"}, + } + + for _, c := range cases { + t.Run("", func(t *testing.T) { + m := checkVersion(c.t, c.s, c.major, c.minor) + if m != c.exp { + t.Fatalf("Got: %s, expected: %s", m, c.exp) + } + }) + } + +} + +func TestVersionMatch(t *testing.T) { + minor := regexVersionMinor + major := regexVersionMajor + client := `Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-06-30T09:51:01Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"darwin/amd64"}` + server := `Server Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-07-26T00:12:31Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}` + + cases := []struct { + r *regexp.Regexp + s string + exp string + }{ + {r: major, s: server, exp: "1"}, + {r: minor, s: server, exp: "7"}, + {r: major, s: client, exp: "1"}, + {r: minor, s: client, exp: "7"}, + {r: major, s: "Some unexpected string"}, + {r: minor}, // Checking that we don't fall over if the string is empty + } + + for _, c := range cases { + t.Run("", func(t *testing.T) { + m := versionMatch(c.r, c.s) + if m != c.exp { + t.Fatalf("Got %s expected %s", m, c.exp) + } + }) + } +}