mirror of
https://github.com/aquasecurity/kube-bench.git
synced 2024-11-23 08:38:06 +00:00
Move cmd/ source code up one level.
This commit is contained in:
parent
47fe0f38ef
commit
5dba85ca47
180
common.go
Normal file
180
common.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/bench-common/check"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errmsgs string
|
||||||
|
)
|
||||||
|
|
||||||
|
func runChecks(nodetype nodeType) {
|
||||||
|
var summary check.Summary
|
||||||
|
var file string
|
||||||
|
var err error
|
||||||
|
var typeConf *viper.Viper
|
||||||
|
|
||||||
|
switch nodetype {
|
||||||
|
case MASTER:
|
||||||
|
file = masterFile
|
||||||
|
case NODE:
|
||||||
|
file = nodeFile
|
||||||
|
case 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([]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.Description))
|
||||||
|
for _, g := range r.Groups {
|
||||||
|
colorPrint(check.INFO, fmt.Sprintf("%s %s\n", g.ID, g.Description))
|
||||||
|
for _, c := range g.Checks {
|
||||||
|
colorPrint(c.State, fmt.Sprintf("%s %s\n", c.ID, c.Description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
60
database.go
Normal file
60
database.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
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"]))
|
||||||
|
}
|
40
federated.go
Normal file
40
federated.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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(FEDERATED)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
federatedCmd.PersistentFlags().StringVarP(&federatedFile,
|
||||||
|
"file",
|
||||||
|
"f",
|
||||||
|
"/federated.yaml",
|
||||||
|
"Alternative YAML file for federated checks",
|
||||||
|
)
|
||||||
|
|
||||||
|
RootCmd.AddCommand(federatedCmd)
|
||||||
|
}
|
6
main.go
6
main.go
@ -14,10 +14,6 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/aquasecurity/kube-bench/cmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.Execute()
|
Execute()
|
||||||
}
|
}
|
||||||
|
40
master.go
Normal file
40
master.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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(MASTER)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
masterCmd.PersistentFlags().StringVarP(&masterFile,
|
||||||
|
"file",
|
||||||
|
"f",
|
||||||
|
"/master.yaml",
|
||||||
|
"Alternative YAML file for master checks",
|
||||||
|
)
|
||||||
|
|
||||||
|
RootCmd.AddCommand(masterCmd)
|
||||||
|
}
|
40
node.go
Normal file
40
node.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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(NODE)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
nodeCmd.PersistentFlags().StringVarP(&nodeFile,
|
||||||
|
"file",
|
||||||
|
"f",
|
||||||
|
"/node.yaml",
|
||||||
|
"Alternative YAML file for node checks",
|
||||||
|
)
|
||||||
|
|
||||||
|
RootCmd.AddCommand(nodeCmd)
|
||||||
|
}
|
115
root.go
Normal file
115
root.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
goflag "flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/bench-common/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)
|
||||||
|
}
|
||||||
|
}
|
12
types.go
Normal file
12
types.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
type nodeType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MASTER a master node
|
||||||
|
MASTER nodeType = "master"
|
||||||
|
// NODE a node
|
||||||
|
NODE nodeType = "node"
|
||||||
|
// FEDERATED a federated deployment.
|
||||||
|
FEDERATED nodeType = "federated"
|
||||||
|
)
|
338
util.go
Normal file
338
util.go
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aquasecurity/bench-common/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
util_test.go
Normal file
352
util_test.go
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
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