mirror of
https://github.com/aquasecurity/kube-bench.git
synced 2025-07-31 19:08:07 +00:00
Delete cmd subdirectory in preparation for reducing package heirarchy.
This commit is contained in:
parent
5782c2124d
commit
47fe0f38ef
180
cmd/common.go
180
cmd/common.go
@ -1,180 +0,0 @@
|
|||||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/kube-bench/check"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errmsgs string
|
|
||||||
)
|
|
||||||
|
|
||||||
func runChecks(nodetype check.NodeType) {
|
|
||||||
var summary check.Summary
|
|
||||||
var file string
|
|
||||||
var err error
|
|
||||||
var typeConf *viper.Viper
|
|
||||||
|
|
||||||
switch nodetype {
|
|
||||||
case check.MASTER:
|
|
||||||
file = masterFile
|
|
||||||
case check.NODE:
|
|
||||||
file = nodeFile
|
|
||||||
case check.FEDERATED:
|
|
||||||
file = federatedFile
|
|
||||||
}
|
|
||||||
|
|
||||||
runningVersion, err := getKubeVersion()
|
|
||||||
if err != nil && kubeVersion == "" {
|
|
||||||
exitWithError(fmt.Errorf("Version check failed: %s\nAlternatively, you can specify the version with --version", err))
|
|
||||||
}
|
|
||||||
path, err := getConfigFilePath(kubeVersion, runningVersion, file)
|
|
||||||
if err != nil {
|
|
||||||
exitWithError(fmt.Errorf("can't find %s controls file in %s: %v", nodetype, cfgDir, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
def := filepath.Join(path, file)
|
|
||||||
in, err := ioutil.ReadFile(def)
|
|
||||||
if err != nil {
|
|
||||||
exitWithError(fmt.Errorf("error opening %s controls file: %v", nodetype, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(1).Info(fmt.Sprintf("Using benchmark file: %s\n", def))
|
|
||||||
|
|
||||||
// Merge kubernetes version specific config if any.
|
|
||||||
viper.SetConfigFile(path + "/config.yaml")
|
|
||||||
err = viper.MergeInConfig()
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
glog.V(2).Info(fmt.Sprintf("No version-specific config.yaml file in %s", path))
|
|
||||||
} else {
|
|
||||||
exitWithError(fmt.Errorf("couldn't read config file %s: %v", path+"/config.yaml", err))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
glog.V(1).Info(fmt.Sprintf("Using config file: %s\n", viper.ConfigFileUsed()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the set of exectuables and config files we care about on this type of node. This also
|
|
||||||
// checks that the executables we need for the node type are running.
|
|
||||||
typeConf = viper.Sub(string(nodetype))
|
|
||||||
binmap := getBinaries(typeConf)
|
|
||||||
confmap := getConfigFiles(typeConf)
|
|
||||||
|
|
||||||
// Variable substitutions. Replace all occurrences of variables in controls files.
|
|
||||||
s := string(in)
|
|
||||||
s = makeSubstitutions(s, "bin", binmap)
|
|
||||||
s = makeSubstitutions(s, "conf", confmap)
|
|
||||||
|
|
||||||
controls, err := check.NewControls(nodetype, []byte(s))
|
|
||||||
if err != nil {
|
|
||||||
exitWithError(fmt.Errorf("error setting up %s controls: %v", nodetype, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if groupList != "" && checkList == "" {
|
|
||||||
ids := cleanIDs(groupList)
|
|
||||||
summary = controls.RunGroup(ids...)
|
|
||||||
} else if checkList != "" && groupList == "" {
|
|
||||||
ids := cleanIDs(checkList)
|
|
||||||
summary = controls.RunChecks(ids...)
|
|
||||||
} else if checkList != "" && groupList != "" {
|
|
||||||
exitWithError(fmt.Errorf("group option and check option can't be used together"))
|
|
||||||
} else {
|
|
||||||
summary = controls.RunGroup()
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we successfully ran some tests and it's json format, ignore the warnings
|
|
||||||
if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0) && jsonFmt {
|
|
||||||
out, err := controls.JSON()
|
|
||||||
if err != nil {
|
|
||||||
exitWithError(fmt.Errorf("failed to output in JSON format: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(string(out))
|
|
||||||
} else {
|
|
||||||
// if we want to store in PostgreSQL, convert to JSON and save it
|
|
||||||
if (summary.Fail > 0 || summary.Warn > 0 || summary.Pass > 0) && pgSQL {
|
|
||||||
out, err := controls.JSON()
|
|
||||||
if err != nil {
|
|
||||||
exitWithError(fmt.Errorf("failed to output in JSON format: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
savePgsql(string(out))
|
|
||||||
} else {
|
|
||||||
prettyPrint(controls, summary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// colorPrint outputs the state in a specific colour, along with a message string
|
|
||||||
func colorPrint(state check.State, s string) {
|
|
||||||
colors[state].Printf("[%s] ", state)
|
|
||||||
fmt.Printf("%s", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prettyPrint outputs the results to stdout in human-readable format
|
|
||||||
func prettyPrint(r *check.Controls, summary check.Summary) {
|
|
||||||
// Print check results.
|
|
||||||
if !noResults {
|
|
||||||
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", r.ID, r.Text))
|
|
||||||
for _, g := range r.Groups {
|
|
||||||
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Text))
|
|
||||||
for _, c := range g.Checks {
|
|
||||||
colorPrint(c.State, fmt.Sprintf("%s %s\n", c.ID, c.Text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print remediations.
|
|
||||||
if !noRemediations {
|
|
||||||
if summary.Fail > 0 || summary.Warn > 0 {
|
|
||||||
colors[check.WARN].Printf("== Remediations ==\n")
|
|
||||||
for _, g := range r.Groups {
|
|
||||||
for _, c := range g.Checks {
|
|
||||||
if c.State != check.PASS {
|
|
||||||
fmt.Printf("%s %s\n", c.ID, c.Remediation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print summary setting output color to highest severity.
|
|
||||||
if !noSummary {
|
|
||||||
var res check.State
|
|
||||||
if summary.Fail > 0 {
|
|
||||||
res = check.FAIL
|
|
||||||
} else if summary.Warn > 0 {
|
|
||||||
res = check.WARN
|
|
||||||
} else {
|
|
||||||
res = check.PASS
|
|
||||||
}
|
|
||||||
|
|
||||||
colors[res].Printf("== Summary ==\n")
|
|
||||||
fmt.Printf("%d checks PASS\n%d checks FAIL\n%d checks WARN\n",
|
|
||||||
summary.Pass, summary.Fail, summary.Warn,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/jinzhu/gorm"
|
|
||||||
_ "github.com/jinzhu/gorm/dialects/postgres" // database packages get blank imports
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func savePgsql(jsonInfo string) {
|
|
||||||
envVars := map[string]string{
|
|
||||||
"PGSQL_HOST": viper.GetString("PGSQL_HOST"),
|
|
||||||
"PGSQL_USER": viper.GetString("PGSQL_USER"),
|
|
||||||
"PGSQL_DBNAME": viper.GetString("PGSQL_DBNAME"),
|
|
||||||
"PGSQL_SSLMODE": viper.GetString("PGSQL_SSLMODE"),
|
|
||||||
"PGSQL_PASSWORD": viper.GetString("PGSQL_PASSWORD"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range envVars {
|
|
||||||
if v == "" {
|
|
||||||
exitWithError(fmt.Errorf("environment variable %s is missing", envVarsPrefix+"_"+k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connInfo := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=%s password=%s",
|
|
||||||
envVars["PGSQL_HOST"],
|
|
||||||
envVars["PGSQL_USER"],
|
|
||||||
envVars["PGSQL_DBNAME"],
|
|
||||||
envVars["PGSQL_SSLMODE"],
|
|
||||||
envVars["PGSQL_PASSWORD"],
|
|
||||||
)
|
|
||||||
|
|
||||||
hostname, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
exitWithError(fmt.Errorf("received error looking up hostname: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp := time.Now()
|
|
||||||
|
|
||||||
type ScanResult struct {
|
|
||||||
gorm.Model
|
|
||||||
ScanHost string `gorm:"type:varchar(63) not null"` // https://www.ietf.org/rfc/rfc1035.txt
|
|
||||||
ScanTime time.Time `gorm:"not null"`
|
|
||||||
ScanInfo string `gorm:"type:jsonb not null"`
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := gorm.Open("postgres", connInfo)
|
|
||||||
defer db.Close()
|
|
||||||
if err != nil {
|
|
||||||
exitWithError(fmt.Errorf("received error connecting to database: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
db.Debug().AutoMigrate(&ScanResult{})
|
|
||||||
db.Save(&ScanResult{ScanHost: hostname, ScanTime: timestamp, ScanInfo: jsonInfo})
|
|
||||||
glog.V(2).Info(fmt.Sprintf("successfully stored result to: %s", envVars["PGSQL_HOST"]))
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"github.com/aquasecurity/kube-bench/check"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// nodeCmd represents the node command
|
|
||||||
var federatedCmd = &cobra.Command{
|
|
||||||
Use: "federated",
|
|
||||||
Short: "Run benchmark checks for a Kubernetes federated deployment.",
|
|
||||||
Long: `Run benchmark checks for a Kubernetes federated deployment.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
runChecks(check.FEDERATED)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
federatedCmd.PersistentFlags().StringVarP(&federatedFile,
|
|
||||||
"file",
|
|
||||||
"f",
|
|
||||||
"/federated.yaml",
|
|
||||||
"Alternative YAML file for federated checks",
|
|
||||||
)
|
|
||||||
|
|
||||||
RootCmd.AddCommand(federatedCmd)
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"github.com/aquasecurity/kube-bench/check"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// masterCmd represents the master command
|
|
||||||
var masterCmd = &cobra.Command{
|
|
||||||
Use: "master",
|
|
||||||
Short: "Run benchmark checks for a Kubernetes master node.",
|
|
||||||
Long: `Run benchmark checks for a Kubernetes master node.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
runChecks(check.MASTER)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
masterCmd.PersistentFlags().StringVarP(&masterFile,
|
|
||||||
"file",
|
|
||||||
"f",
|
|
||||||
"/master.yaml",
|
|
||||||
"Alternative YAML file for master checks",
|
|
||||||
)
|
|
||||||
|
|
||||||
RootCmd.AddCommand(masterCmd)
|
|
||||||
}
|
|
41
cmd/node.go
41
cmd/node.go
@ -1,41 +0,0 @@
|
|||||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"github.com/aquasecurity/kube-bench/check"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// nodeCmd represents the node command
|
|
||||||
var nodeCmd = &cobra.Command{
|
|
||||||
Use: "node",
|
|
||||||
Short: "Run benchmark checks for a Kubernetes node.",
|
|
||||||
Long: `Run benchmark checks for a Kubernetes node.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
runChecks(check.NODE)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
nodeCmd.PersistentFlags().StringVarP(&nodeFile,
|
|
||||||
"file",
|
|
||||||
"f",
|
|
||||||
"/node.yaml",
|
|
||||||
"Alternative YAML file for node checks",
|
|
||||||
)
|
|
||||||
|
|
||||||
RootCmd.AddCommand(nodeCmd)
|
|
||||||
}
|
|
115
cmd/root.go
115
cmd/root.go
@ -1,115 +0,0 @@
|
|||||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
goflag "flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/kube-bench/check"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
envVarsPrefix = "KUBE_BENCH"
|
|
||||||
defaultKubeVersion = "1.6"
|
|
||||||
kubeVersion string
|
|
||||||
cfgFile string
|
|
||||||
cfgDir string
|
|
||||||
jsonFmt bool
|
|
||||||
pgSQL bool
|
|
||||||
checkList string
|
|
||||||
groupList string
|
|
||||||
masterFile string
|
|
||||||
nodeFile string
|
|
||||||
federatedFile string
|
|
||||||
noResults bool
|
|
||||||
noSummary bool
|
|
||||||
noRemediations bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// RootCmd represents the base command when called without any subcommands
|
|
||||||
var RootCmd = &cobra.Command{
|
|
||||||
Use: os.Args[0],
|
|
||||||
Short: "Run CIS Benchmarks checks against a Kubernetes deployment",
|
|
||||||
Long: `This tool runs the CIS Kubernetes Benchmark (http://www.cisecurity.org/benchmark/kubernetes/)`,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute adds all child commands to the root command sets flags appropriately.
|
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
|
||||||
func Execute() {
|
|
||||||
goflag.Set("logtostderr", "true")
|
|
||||||
goflag.CommandLine.Parse([]string{})
|
|
||||||
|
|
||||||
if err := RootCmd.Execute(); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
cobra.OnInitialize(initConfig)
|
|
||||||
|
|
||||||
// Output control
|
|
||||||
RootCmd.PersistentFlags().BoolVar(&noResults, "noresults", false, "Disable printing of results section")
|
|
||||||
RootCmd.PersistentFlags().BoolVar(&noSummary, "nosummary", false, "Disable printing of summary section")
|
|
||||||
RootCmd.PersistentFlags().BoolVar(&noRemediations, "noremediations", false, "Disable printing of remediations section")
|
|
||||||
RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
|
|
||||||
RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL")
|
|
||||||
|
|
||||||
RootCmd.PersistentFlags().StringVarP(
|
|
||||||
&checkList,
|
|
||||||
"check",
|
|
||||||
"c",
|
|
||||||
"",
|
|
||||||
`A comma-delimited list of checks to run as specified in CIS document. Example --check="1.1.1,1.1.2"`,
|
|
||||||
)
|
|
||||||
RootCmd.PersistentFlags().StringVarP(
|
|
||||||
&groupList,
|
|
||||||
"group",
|
|
||||||
"g",
|
|
||||||
"",
|
|
||||||
`Run all the checks under this comma-delimited list of groups. Example --group="1.1"`,
|
|
||||||
)
|
|
||||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./cfg/config.yaml)")
|
|
||||||
RootCmd.PersistentFlags().StringVarP(&cfgDir, "config-dir", "D", "./cfg/", "config directory")
|
|
||||||
RootCmd.PersistentFlags().StringVar(&kubeVersion, "version", "", "Manually specify Kubernetes version, automatically detected if unset")
|
|
||||||
|
|
||||||
goflag.CommandLine.VisitAll(func(goflag *goflag.Flag) {
|
|
||||||
RootCmd.PersistentFlags().AddGoFlag(goflag)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
|
||||||
func initConfig() {
|
|
||||||
if cfgFile != "" { // enable ability to specify config file via flag
|
|
||||||
viper.SetConfigFile(cfgFile)
|
|
||||||
} else {
|
|
||||||
viper.SetConfigName("config") // name of config file (without extension)
|
|
||||||
viper.AddConfigPath(cfgDir) // adding ./cfg as first search path
|
|
||||||
}
|
|
||||||
|
|
||||||
viper.SetEnvPrefix(envVarsPrefix)
|
|
||||||
viper.AutomaticEnv() // read in environment variables that match
|
|
||||||
|
|
||||||
// If a config file is found, read it in.
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
|
||||||
colorPrint(check.FAIL, fmt.Sprintf("Failed to read config file: %v\n", err))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
338
cmd/util.go
338
cmd/util.go
@ -1,338 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/aquasecurity/kube-bench/check"
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Print colors
|
|
||||||
colors = map[check.State]*color.Color{
|
|
||||||
check.PASS: color.New(color.FgGreen),
|
|
||||||
check.FAIL: color.New(color.FgRed),
|
|
||||||
check.WARN: color.New(color.FgYellow),
|
|
||||||
check.INFO: color.New(color.FgBlue),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var psFunc func(string) string
|
|
||||||
var statFunc func(string) (os.FileInfo, error)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
psFunc = ps
|
|
||||||
statFunc = os.Stat
|
|
||||||
}
|
|
||||||
|
|
||||||
func printlnWarn(msg string) {
|
|
||||||
fmt.Fprintf(os.Stderr, "[%s] %s\n",
|
|
||||||
colors[check.WARN].Sprintf("%s", check.WARN),
|
|
||||||
msg,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sprintlnWarn(msg string) string {
|
|
||||||
return fmt.Sprintf("[%s] %s",
|
|
||||||
colors[check.WARN].Sprintf("%s", check.WARN),
|
|
||||||
msg,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func exitWithError(err error) {
|
|
||||||
fmt.Fprintf(os.Stderr, "\n%v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func continueWithError(err error, msg string) string {
|
|
||||||
if err != nil {
|
|
||||||
glog.V(2).Info(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanIDs(list string) []string {
|
|
||||||
list = strings.Trim(list, ",")
|
|
||||||
ids := strings.Split(list, ",")
|
|
||||||
|
|
||||||
for _, id := range ids {
|
|
||||||
id = strings.Trim(id, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ids
|
|
||||||
}
|
|
||||||
|
|
||||||
// ps execs out to the ps command; it's separated into a function so we can write tests
|
|
||||||
func ps(proc string) string {
|
|
||||||
cmd := exec.Command("ps", "-C", proc, "-o", "cmd", "--no-headers")
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
continueWithError(fmt.Errorf("%s: %s", cmd.Args, err), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConfigFilePath locates the config files we should be using based on either the specified
|
|
||||||
// version, or the running version of kubernetes if not specified
|
|
||||||
func getConfigFilePath(specifiedVersion string, runningVersion string, filename string) (path string, err error) {
|
|
||||||
var fileVersion string
|
|
||||||
|
|
||||||
if specifiedVersion != "" {
|
|
||||||
fileVersion = specifiedVersion
|
|
||||||
} else {
|
|
||||||
fileVersion = runningVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(2).Info(fmt.Sprintf("Looking for config for version %s", fileVersion))
|
|
||||||
|
|
||||||
for {
|
|
||||||
path = filepath.Join(cfgDir, fileVersion)
|
|
||||||
file := filepath.Join(path, string(filename))
|
|
||||||
glog.V(2).Info(fmt.Sprintf("Looking for config file: %s\n", file))
|
|
||||||
|
|
||||||
if _, err = os.Stat(file); !os.IsNotExist(err) {
|
|
||||||
if specifiedVersion == "" && fileVersion != runningVersion {
|
|
||||||
glog.V(1).Info(fmt.Sprintf("No test file found for %s - using tests for Kubernetes %s\n", runningVersion, fileVersion))
|
|
||||||
}
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we were given an explicit version to look for, don't look for any others
|
|
||||||
if specifiedVersion != "" {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileVersion = decrementVersion(fileVersion)
|
|
||||||
if fileVersion == "" {
|
|
||||||
return "", fmt.Errorf("no test files found <= runningVersion")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrementVersion decrements the version number
|
|
||||||
// We want to decrement individually even through versions where we don't supply test files
|
|
||||||
// just in case someone wants to specify their own test files for that version
|
|
||||||
func decrementVersion(version string) string {
|
|
||||||
split := strings.Split(version, ".")
|
|
||||||
minor, err := strconv.Atoi(split[1])
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if minor <= 1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
split[1] = strconv.Itoa(minor - 1)
|
|
||||||
return strings.Join(split, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConfigFiles finds which of the set of candidate config files exist
|
|
||||||
// accepts a string 't' which indicates the type of config file, conf,
|
|
||||||
// podspec or untifile.
|
|
||||||
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
|
|
||||||
glog.V(2).Info(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
|
|
||||||
func verifyBin(bin string) bool {
|
|
||||||
|
|
||||||
// Strip any quotes
|
|
||||||
bin = strings.Trim(bin, "'\"")
|
|
||||||
|
|
||||||
// bin could consist of more than one word
|
|
||||||
// We'll search for running processes with the first word, and then check the whole
|
|
||||||
// proc as supplied is included in the results
|
|
||||||
proc := strings.Fields(bin)[0]
|
|
||||||
out := psFunc(proc)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
glog.V(1).Info(fmt.Sprintf("executable '%s' not running", c))
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("no candidates running")
|
|
||||||
}
|
|
||||||
|
|
||||||
func multiWordReplace(s string, subname string, sub string) string {
|
|
||||||
f := strings.Fields(sub)
|
|
||||||
if len(f) > 1 {
|
|
||||||
sub = "'" + sub + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Replace(s, subname, sub, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKubeVersion() (string, error) {
|
|
||||||
// These executables might not be on the user's path.
|
|
||||||
_, err := exec.LookPath("kubectl")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
_, err = exec.LookPath("kubelet")
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("need kubectl or kubelet binaries to get kubernetes version")
|
|
||||||
}
|
|
||||||
return getKubeVersionFromKubelet(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return getKubeVersionFromKubectl(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKubeVersionFromKubectl() string {
|
|
||||||
cmd := exec.Command("kubectl", "version", "--short")
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
continueWithError(fmt.Errorf("%s", out), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return getVersionFromKubectlOutput(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKubeVersionFromKubelet() string {
|
|
||||||
cmd := exec.Command("kubelet", "--version")
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
continueWithError(fmt.Errorf("%s", out), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return getVersionFromKubeletOutput(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVersionFromKubectlOutput(s string) string {
|
|
||||||
serverVersionRe := regexp.MustCompile(`Server Version: v(\d+.\d+)`)
|
|
||||||
subs := serverVersionRe.FindStringSubmatch(s)
|
|
||||||
if len(subs) < 2 {
|
|
||||||
printlnWarn(fmt.Sprintf("Unable to get kubectl version, using default version: %s", defaultKubeVersion))
|
|
||||||
return defaultKubeVersion
|
|
||||||
}
|
|
||||||
return subs[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVersionFromKubeletOutput(s string) string {
|
|
||||||
serverVersionRe := regexp.MustCompile(`Kubernetes v(\d+.\d+)`)
|
|
||||||
subs := serverVersionRe.FindStringSubmatch(s)
|
|
||||||
if len(subs) < 2 {
|
|
||||||
printlnWarn(fmt.Sprintf("Unable to get kubelet version, using default version: %s", defaultKubeVersion))
|
|
||||||
return defaultKubeVersion
|
|
||||||
}
|
|
||||||
return subs[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(2).Info(fmt.Sprintf("Substituting %s with '%s'\n", subst, v))
|
|
||||||
s = multiWordReplace(s, subst, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
352
cmd/util_test.go
352
cmd/util_test.go
@ -1,352 +0,0 @@
|
|||||||
// Copyright © 2017 Aqua Security Software Ltd. <info@aquasec.com>
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var g string
|
|
||||||
var e []error
|
|
||||||
var eIndex int
|
|
||||||
|
|
||||||
func fakeps(proc string) string {
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func fakestat(file string) (os.FileInfo, error) {
|
|
||||||
err := e[eIndex]
|
|
||||||
eIndex++
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyBin(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
proc string
|
|
||||||
psOut string
|
|
||||||
exp bool
|
|
||||||
}{
|
|
||||||
{proc: "single", psOut: "single", exp: true},
|
|
||||||
{proc: "single", psOut: "", exp: false},
|
|
||||||
{proc: "two words", psOut: "two words", exp: true},
|
|
||||||
{proc: "two words", psOut: "", exp: false},
|
|
||||||
{proc: "cmd", psOut: "cmd param1 param2", exp: true},
|
|
||||||
{proc: "cmd param", psOut: "cmd param1 param2", exp: true},
|
|
||||||
{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 {
|
|
||||||
t.Run(strconv.Itoa(id), func(t *testing.T) {
|
|
||||||
g = c.psOut
|
|
||||||
for k, val := range c.config {
|
|
||||||
v.Set(k, val)
|
|
||||||
}
|
|
||||||
m := getBinaries(v)
|
|
||||||
if !reflect.DeepEqual(m, c.exp) {
|
|
||||||
t.Fatalf("Got %v\nExpected %v", m, c.exp)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiWordReplace(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
input string
|
|
||||||
sub string
|
|
||||||
subname string
|
|
||||||
output string
|
|
||||||
}{
|
|
||||||
{input: "Here's a file with no substitutions", sub: "blah", subname: "blah", output: "Here's a file with no substitutions"},
|
|
||||||
{input: "Here's a file with a substitution", sub: "blah", subname: "substitution", output: "Here's a file with a blah"},
|
|
||||||
{input: "Here's a file with multi-word substitutions", sub: "multi word", subname: "multi-word", output: "Here's a file with 'multi word' substitutions"},
|
|
||||||
{input: "Here's a file with several several substitutions several", sub: "blah", subname: "several", output: "Here's a file with blah blah substitutions blah"},
|
|
||||||
}
|
|
||||||
for id, c := range cases {
|
|
||||||
t.Run(strconv.Itoa(id), func(t *testing.T) {
|
|
||||||
s := multiWordReplace(c.input, c.subname, c.sub)
|
|
||||||
if s != c.output {
|
|
||||||
t.Fatalf("Expected %s got %s", c.output, s)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKubeVersionRegex(t *testing.T) {
|
|
||||||
ver := getVersionFromKubectlOutput(`Client Version: v1.8.0
|
|
||||||
Server Version: v1.8.12
|
|
||||||
`)
|
|
||||||
if ver != "1.8" {
|
|
||||||
t.Fatalf("Expected 1.8 got %s", ver)
|
|
||||||
}
|
|
||||||
|
|
||||||
ver = getVersionFromKubectlOutput("Something completely different")
|
|
||||||
if ver != "1.6" {
|
|
||||||
t.Fatalf("Expected 1.6 got %s", ver)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetConfigFilePath(t *testing.T) {
|
|
||||||
var err error
|
|
||||||
cfgDir, err = ioutil.TempDir("", "kube-bench-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create temp directory")
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(cfgDir)
|
|
||||||
d := filepath.Join(cfgDir, "1.8")
|
|
||||||
err = os.Mkdir(d, 0666)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create temp file")
|
|
||||||
}
|
|
||||||
ioutil.WriteFile(filepath.Join(d, "master.yaml"), []byte("hello world"), 0666)
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
specifiedVersion string
|
|
||||||
runningVersion string
|
|
||||||
succeed bool
|
|
||||||
exp string
|
|
||||||
}{
|
|
||||||
{runningVersion: "1.8", succeed: true, exp: d},
|
|
||||||
{runningVersion: "1.9", succeed: true, exp: d},
|
|
||||||
{runningVersion: "1.10", succeed: true, exp: d},
|
|
||||||
{runningVersion: "1.1", succeed: false},
|
|
||||||
{specifiedVersion: "1.8", succeed: true, exp: d},
|
|
||||||
{specifiedVersion: "1.9", succeed: false},
|
|
||||||
{specifiedVersion: "1.10", succeed: false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.specifiedVersion+"-"+c.runningVersion, func(t *testing.T) {
|
|
||||||
path, err := getConfigFilePath(c.specifiedVersion, c.runningVersion, "/master.yaml")
|
|
||||||
if err != nil && c.succeed {
|
|
||||||
t.Fatalf("Error %v", err)
|
|
||||||
}
|
|
||||||
if path != c.exp {
|
|
||||||
t.Fatalf("Got %s expected %s", path, c.exp)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user