mirror of
https://github.com/aquasecurity/kube-bench.git
synced 2025-01-27 16:11:09 +00:00
Remove kube-bench internal check code.
This commit is contained in:
parent
2f4f55a363
commit
5782c2124d
242
check/check.go
242
check/check.go
@ -1,242 +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 check
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeType indicates the type of node (master, node, federated).
|
|
||||||
type NodeType string
|
|
||||||
|
|
||||||
// State is the state of a control check.
|
|
||||||
type State string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PASS check passed.
|
|
||||||
PASS State = "PASS"
|
|
||||||
// FAIL check failed.
|
|
||||||
FAIL = "FAIL"
|
|
||||||
// WARN could not carry out check.
|
|
||||||
WARN = "WARN"
|
|
||||||
// INFO informational message
|
|
||||||
INFO = "INFO"
|
|
||||||
|
|
||||||
// MASTER a master node
|
|
||||||
MASTER NodeType = "master"
|
|
||||||
// NODE a node
|
|
||||||
NODE NodeType = "node"
|
|
||||||
// FEDERATED a federated deployment.
|
|
||||||
FEDERATED NodeType = "federated"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleError(err error, context string) (errmsg string) {
|
|
||||||
if err != nil {
|
|
||||||
errmsg = fmt.Sprintf("%s, error: %s\n", context, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check contains information about a recommendation in the
|
|
||||||
// CIS Kubernetes 1.6+ document.
|
|
||||||
type Check struct {
|
|
||||||
ID string `yaml:"id" json:"test_number"`
|
|
||||||
Text string `json:"test_desc"`
|
|
||||||
Audit string `json:"omit"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Commands []*exec.Cmd `json:"omit"`
|
|
||||||
Tests *tests `json:"omit"`
|
|
||||||
Set bool `json:"omit"`
|
|
||||||
Remediation string `json:"-"`
|
|
||||||
TestInfo []string `json:"test_info"`
|
|
||||||
State `json:"status"`
|
|
||||||
ActualValue string `json:"actual_value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run executes the audit commands specified in a check and outputs
|
|
||||||
// the results.
|
|
||||||
func (c *Check) Run() {
|
|
||||||
// If check type is manual, force result to WARN.
|
|
||||||
if c.Type == "manual" {
|
|
||||||
c.State = WARN
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var out bytes.Buffer
|
|
||||||
var errmsgs string
|
|
||||||
|
|
||||||
// Check if command exists or exit with WARN.
|
|
||||||
for _, cmd := range c.Commands {
|
|
||||||
if !isShellCommand(cmd.Path) {
|
|
||||||
c.State = WARN
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run commands.
|
|
||||||
n := len(c.Commands)
|
|
||||||
if n == 0 {
|
|
||||||
// Likely a warning message.
|
|
||||||
c.State = WARN
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each command runs,
|
|
||||||
// cmd0 out -> cmd1 in, cmd1 out -> cmd2 in ... cmdn out -> os.stdout
|
|
||||||
// cmd0 err should terminate chain
|
|
||||||
cs := c.Commands
|
|
||||||
|
|
||||||
// Initialize command pipeline
|
|
||||||
cs[n-1].Stdout = &out
|
|
||||||
i := 1
|
|
||||||
|
|
||||||
var err error
|
|
||||||
errmsgs = ""
|
|
||||||
|
|
||||||
for i < n {
|
|
||||||
cs[i-1].Stdout, err = cs[i].StdinPipe()
|
|
||||||
errmsgs += handleError(
|
|
||||||
err,
|
|
||||||
fmt.Sprintf("failed to run: %s\nfailed command: %s",
|
|
||||||
c.Audit,
|
|
||||||
cs[i].Args,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start command pipeline
|
|
||||||
i = 0
|
|
||||||
for i < n {
|
|
||||||
err := cs[i].Start()
|
|
||||||
errmsgs += handleError(
|
|
||||||
err,
|
|
||||||
fmt.Sprintf("failed to run: %s\nfailed command: %s",
|
|
||||||
c.Audit,
|
|
||||||
cs[i].Args,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete command pipeline
|
|
||||||
i = 0
|
|
||||||
for i < n {
|
|
||||||
err := cs[i].Wait()
|
|
||||||
errmsgs += handleError(
|
|
||||||
err,
|
|
||||||
fmt.Sprintf("failed to run: %s\nfailed command:%s",
|
|
||||||
c.Audit,
|
|
||||||
cs[i].Args,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if i < n-1 {
|
|
||||||
cs[i].Stdout.(io.Closer).Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
finalOutput := c.Tests.execute(out.String())
|
|
||||||
if finalOutput != nil {
|
|
||||||
c.ActualValue = finalOutput.actualResult
|
|
||||||
if finalOutput.testResult {
|
|
||||||
c.State = PASS
|
|
||||||
} else {
|
|
||||||
c.State = FAIL
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errmsgs += handleError(
|
|
||||||
fmt.Errorf("final output is nil"),
|
|
||||||
fmt.Sprintf("failed to run: %s\n",
|
|
||||||
c.Audit,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errmsgs != "" {
|
|
||||||
glog.V(2).Info(errmsgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// textToCommand transforms an input text representation of commands to be
|
|
||||||
// run into a slice of commands.
|
|
||||||
// TODO: Make this more robust.
|
|
||||||
func textToCommand(s string) []*exec.Cmd {
|
|
||||||
cmds := []*exec.Cmd{}
|
|
||||||
|
|
||||||
cp := strings.Split(s, "|")
|
|
||||||
|
|
||||||
for _, v := range cp {
|
|
||||||
v = strings.Trim(v, " ")
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// GOAL: To split input text into arguments for exec.Cmd.
|
|
||||||
//
|
|
||||||
// CHALLENGE: The input text may contain quoted strings that
|
|
||||||
// must be passed as a unit to exec.Cmd.
|
|
||||||
// eg. bash -c 'foo bar'
|
|
||||||
// 'foo bar' must be passed as unit to exec.Cmd if not the command
|
|
||||||
// will fail when it is executed.
|
|
||||||
// eg. exec.Cmd("bash", "-c", "foo bar")
|
|
||||||
//
|
|
||||||
// PROBLEM: Current solution assumes the grouped string will always
|
|
||||||
// be at the end of the input text.
|
|
||||||
re := regexp.MustCompile(`^(.*)(['"].*['"])$`)
|
|
||||||
grps := re.FindStringSubmatch(v)
|
|
||||||
|
|
||||||
var cs []string
|
|
||||||
if len(grps) > 0 {
|
|
||||||
s := strings.Trim(grps[1], " ")
|
|
||||||
cs = strings.Split(s, " ")
|
|
||||||
|
|
||||||
s1 := grps[len(grps)-1]
|
|
||||||
s1 = strings.Trim(s1, "'\"")
|
|
||||||
|
|
||||||
cs = append(cs, s1)
|
|
||||||
} else {
|
|
||||||
cs = strings.Split(v, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(cs[0], cs[1:]...)
|
|
||||||
cmds = append(cmds, cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
func isShellCommand(s string) bool {
|
|
||||||
cmd := exec.Command("/bin/sh", "-c", "command -v "+s)
|
|
||||||
|
|
||||||
out, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(string(out), s) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
@ -1,197 +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 check
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Controls holds all controls to check for master nodes.
|
|
||||||
type Controls struct {
|
|
||||||
ID string `yaml:"id" json:"id"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
Type NodeType `json:"node_type"`
|
|
||||||
Groups []*Group `json:"tests"`
|
|
||||||
Summary
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group is a collection of similar checks.
|
|
||||||
type Group struct {
|
|
||||||
ID string `yaml:"id" json:"section"`
|
|
||||||
Pass int `json:"pass"`
|
|
||||||
Fail int `json:"fail"`
|
|
||||||
Warn int `json:"warn"`
|
|
||||||
Text string `json:"desc"`
|
|
||||||
Checks []*Check `json:"results"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summary is a summary of the results of control checks run.
|
|
||||||
type Summary struct {
|
|
||||||
Pass int `json:"total_pass"`
|
|
||||||
Fail int `json:"total_fail"`
|
|
||||||
Warn int `json:"total_warn"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewControls instantiates a new master Controls object.
|
|
||||||
func NewControls(t NodeType, in []byte) (*Controls, error) {
|
|
||||||
c := new(Controls)
|
|
||||||
|
|
||||||
err := yaml.Unmarshal(in, c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to unmarshal YAML: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t != c.Type {
|
|
||||||
return nil, fmt.Errorf("non-%s controls file specified", t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare audit commands
|
|
||||||
for _, group := range c.Groups {
|
|
||||||
for _, check := range group.Checks {
|
|
||||||
check.Commands = textToCommand(check.Audit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunGroup runs all checks in a group.
|
|
||||||
func (controls *Controls) RunGroup(gids ...string) Summary {
|
|
||||||
g := []*Group{}
|
|
||||||
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 0, 0, 0
|
|
||||||
|
|
||||||
// If no groupid is passed run all group checks.
|
|
||||||
if len(gids) == 0 {
|
|
||||||
gids = controls.getAllGroupIDs()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, group := range controls.Groups {
|
|
||||||
|
|
||||||
for _, gid := range gids {
|
|
||||||
if gid == group.ID {
|
|
||||||
for _, check := range group.Checks {
|
|
||||||
check.Run()
|
|
||||||
check.TestInfo = append(check.TestInfo, check.Remediation)
|
|
||||||
summarize(controls, check)
|
|
||||||
summarizeGroup(group, check)
|
|
||||||
}
|
|
||||||
|
|
||||||
g = append(g, group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
controls.Groups = g
|
|
||||||
return controls.Summary
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunChecks runs the checks with the supplied IDs.
|
|
||||||
func (controls *Controls) RunChecks(ids ...string) Summary {
|
|
||||||
g := []*Group{}
|
|
||||||
m := make(map[string]*Group)
|
|
||||||
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn = 0, 0, 0
|
|
||||||
|
|
||||||
// If no groupid is passed run all group checks.
|
|
||||||
if len(ids) == 0 {
|
|
||||||
ids = controls.getAllCheckIDs()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, group := range controls.Groups {
|
|
||||||
for _, check := range group.Checks {
|
|
||||||
for _, id := range ids {
|
|
||||||
if id == check.ID {
|
|
||||||
check.Run()
|
|
||||||
check.TestInfo = append(check.TestInfo, check.Remediation)
|
|
||||||
summarize(controls, check)
|
|
||||||
|
|
||||||
// Check if we have already added this checks group.
|
|
||||||
if v, ok := m[group.ID]; !ok {
|
|
||||||
// Create a group with same info
|
|
||||||
w := &Group{
|
|
||||||
ID: group.ID,
|
|
||||||
Text: group.Text,
|
|
||||||
Checks: []*Check{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this check to the new group
|
|
||||||
w.Checks = append(w.Checks, check)
|
|
||||||
|
|
||||||
// Add to groups we have visited.
|
|
||||||
m[w.ID] = w
|
|
||||||
g = append(g, w)
|
|
||||||
} else {
|
|
||||||
v.Checks = append(v.Checks, check)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
controls.Groups = g
|
|
||||||
return controls.Summary
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON encodes the results of last run to JSON.
|
|
||||||
func (controls *Controls) JSON() ([]byte, error) {
|
|
||||||
return json.Marshal(controls)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (controls *Controls) getAllGroupIDs() []string {
|
|
||||||
var ids []string
|
|
||||||
|
|
||||||
for _, group := range controls.Groups {
|
|
||||||
ids = append(ids, group.ID)
|
|
||||||
}
|
|
||||||
return ids
|
|
||||||
}
|
|
||||||
|
|
||||||
func (controls *Controls) getAllCheckIDs() []string {
|
|
||||||
var ids []string
|
|
||||||
|
|
||||||
for _, group := range controls.Groups {
|
|
||||||
for _, check := range group.Checks {
|
|
||||||
ids = append(ids, check.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ids
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func summarize(controls *Controls, check *Check) {
|
|
||||||
switch check.State {
|
|
||||||
case PASS:
|
|
||||||
controls.Summary.Pass++
|
|
||||||
case FAIL:
|
|
||||||
controls.Summary.Fail++
|
|
||||||
case WARN:
|
|
||||||
controls.Summary.Warn++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func summarizeGroup(group *Group, check *Check) {
|
|
||||||
switch check.State {
|
|
||||||
case PASS:
|
|
||||||
group.Pass++
|
|
||||||
case FAIL:
|
|
||||||
group.Fail++
|
|
||||||
case WARN:
|
|
||||||
group.Warn++
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package check
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const cfgDir = "../cfg/"
|
|
||||||
|
|
||||||
// validate that the files we're shipping are valid YAML
|
|
||||||
func TestYamlFiles(t *testing.T) {
|
|
||||||
// TODO: make this list dynamic
|
|
||||||
dirs := []string{"1.6/", "1.7/"}
|
|
||||||
|
|
||||||
for _, dir := range dirs {
|
|
||||||
dir = cfgDir + dir
|
|
||||||
|
|
||||||
files, err := ioutil.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error reading %s directory: %v", dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
|
|
||||||
fileName := file.Name()
|
|
||||||
in, err := ioutil.ReadFile(dir + fileName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error opening file %s: %v", fileName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := new(Controls)
|
|
||||||
|
|
||||||
err = yaml.Unmarshal(in, c)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to load YAML from %s: %v", fileName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
160
check/data
160
check/data
@ -1,160 +0,0 @@
|
|||||||
---
|
|
||||||
controls:
|
|
||||||
id: 1
|
|
||||||
text: "Master Checks"
|
|
||||||
type: "master"
|
|
||||||
groups:
|
|
||||||
- id: 1.1
|
|
||||||
text: "Kube-apiserver"
|
|
||||||
checks:
|
|
||||||
- id: 0
|
|
||||||
text: "flag is set"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--allow-privileged"
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 1
|
|
||||||
text: "flag is not set"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--basic-auth"
|
|
||||||
set: false
|
|
||||||
|
|
||||||
- id: 2
|
|
||||||
text: "flag value is set to some value"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--insecure-port"
|
|
||||||
compare:
|
|
||||||
op: eq
|
|
||||||
value: 0
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 3
|
|
||||||
text: "flag value is greater than or equal some number"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--audit-log-maxage"
|
|
||||||
compare:
|
|
||||||
op: gte
|
|
||||||
value: 30
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 4
|
|
||||||
text: "flag value is less than some number"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--max-backlog"
|
|
||||||
compare:
|
|
||||||
op: lt
|
|
||||||
value: 30
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 5
|
|
||||||
text: "flag value does not have some value"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--admission-control"
|
|
||||||
compare:
|
|
||||||
op: nothave
|
|
||||||
value: AlwaysAdmit
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 6
|
|
||||||
text: "test AND binary operation"
|
|
||||||
tests:
|
|
||||||
bin_op: and
|
|
||||||
test_items:
|
|
||||||
- flag: "--kubelet-client-certificate"
|
|
||||||
set: true
|
|
||||||
- flag: "--kubelet-clientkey"
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 7
|
|
||||||
text: "test OR binary operation"
|
|
||||||
tests:
|
|
||||||
bin_op: or
|
|
||||||
test_items:
|
|
||||||
- flag: "--secure-port"
|
|
||||||
compare:
|
|
||||||
op: eq
|
|
||||||
value: 0
|
|
||||||
set: true
|
|
||||||
-
|
|
||||||
flag: "--secure-port"
|
|
||||||
set: false
|
|
||||||
|
|
||||||
- id: 8
|
|
||||||
text: "test flag with arbitrary text"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "644"
|
|
||||||
compare:
|
|
||||||
op: eq
|
|
||||||
value: "644"
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 9
|
|
||||||
text: "test permissions"
|
|
||||||
audit: "/bin/sh -c 'if test -e $config; then stat -c %a $config; fi'"
|
|
||||||
tests:
|
|
||||||
bin_op: or
|
|
||||||
test_items:
|
|
||||||
- flag: "644"
|
|
||||||
compare:
|
|
||||||
op: eq
|
|
||||||
value: "644"
|
|
||||||
set: true
|
|
||||||
- flag: "640"
|
|
||||||
compare:
|
|
||||||
op: eq
|
|
||||||
value: "640"
|
|
||||||
set: true
|
|
||||||
- flag: "600"
|
|
||||||
compare:
|
|
||||||
op: eq
|
|
||||||
value: "600"
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 10
|
|
||||||
text: "flag value includes some value in a comma-separated list, value is last in list"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--admission-control"
|
|
||||||
compare:
|
|
||||||
op: has
|
|
||||||
value: RBAC
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 11
|
|
||||||
text: "flag value includes some value in a comma-separated list, value is first in list"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--admission-control"
|
|
||||||
compare:
|
|
||||||
op: has
|
|
||||||
value: WebHook
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 12
|
|
||||||
text: "flag value includes some value in a comma-separated list, value middle of list"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--admission-control"
|
|
||||||
compare:
|
|
||||||
op: has
|
|
||||||
value: Something
|
|
||||||
set: true
|
|
||||||
|
|
||||||
- id: 13
|
|
||||||
text: "flag value includes some value in a comma-separated list, value only one in list"
|
|
||||||
tests:
|
|
||||||
test_items:
|
|
||||||
- flag: "--admission-control"
|
|
||||||
compare:
|
|
||||||
op: has
|
|
||||||
value: Something
|
|
||||||
set: true
|
|
||||||
|
|
||||||
|
|
194
check/test.go
194
check/test.go
@ -1,194 +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 check
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// test:
|
|
||||||
// flag: OPTION
|
|
||||||
// set: (true|false)
|
|
||||||
// compare:
|
|
||||||
// op: (eq|gt|gte|lt|lte|has)
|
|
||||||
// value: val
|
|
||||||
|
|
||||||
type binOp string
|
|
||||||
|
|
||||||
const (
|
|
||||||
and binOp = "and"
|
|
||||||
or = "or"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testItem struct {
|
|
||||||
Flag string
|
|
||||||
Output string
|
|
||||||
Value string
|
|
||||||
Set bool
|
|
||||||
Compare compare
|
|
||||||
}
|
|
||||||
|
|
||||||
type compare struct {
|
|
||||||
Op string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
type testOutput struct {
|
|
||||||
testResult bool
|
|
||||||
actualResult string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *testItem) execute(s string) *testOutput {
|
|
||||||
result := &testOutput{}
|
|
||||||
match := strings.Contains(s, t.Flag)
|
|
||||||
|
|
||||||
if t.Set {
|
|
||||||
var flagVal string
|
|
||||||
isset := match
|
|
||||||
|
|
||||||
if isset && t.Compare.Op != "" {
|
|
||||||
// Expects flags in the form;
|
|
||||||
// --flag=somevalue
|
|
||||||
// --flag
|
|
||||||
// somevalue
|
|
||||||
//pttn := `(` + t.Flag + `)(=)*([^\s,]*) *`
|
|
||||||
pttn := `(` + t.Flag + `)(=)*([^\s]*) *`
|
|
||||||
flagRe := regexp.MustCompile(pttn)
|
|
||||||
vals := flagRe.FindStringSubmatch(s)
|
|
||||||
|
|
||||||
if len(vals) > 0 {
|
|
||||||
if vals[3] != "" {
|
|
||||||
flagVal = vals[3]
|
|
||||||
} else {
|
|
||||||
flagVal = vals[1]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "invalid flag in testitem definition")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
result.actualResult = strings.ToLower(flagVal)
|
|
||||||
switch t.Compare.Op {
|
|
||||||
case "eq":
|
|
||||||
value := strings.ToLower(flagVal)
|
|
||||||
// Do case insensitive comparaison for booleans ...
|
|
||||||
if value == "false" || value == "true" {
|
|
||||||
result.testResult = value == t.Compare.Value
|
|
||||||
} else {
|
|
||||||
result.testResult = flagVal == t.Compare.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
case "noteq":
|
|
||||||
value := strings.ToLower(flagVal)
|
|
||||||
// Do case insensitive comparaison for booleans ...
|
|
||||||
if value == "false" || value == "true" {
|
|
||||||
result.testResult = !(value == t.Compare.Value)
|
|
||||||
} else {
|
|
||||||
result.testResult = !(flagVal == t.Compare.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "gt":
|
|
||||||
a, b := toNumeric(flagVal, t.Compare.Value)
|
|
||||||
result.testResult = a > b
|
|
||||||
|
|
||||||
case "gte":
|
|
||||||
a, b := toNumeric(flagVal, t.Compare.Value)
|
|
||||||
result.testResult = a >= b
|
|
||||||
|
|
||||||
case "lt":
|
|
||||||
a, b := toNumeric(flagVal, t.Compare.Value)
|
|
||||||
result.testResult = a < b
|
|
||||||
|
|
||||||
case "lte":
|
|
||||||
a, b := toNumeric(flagVal, t.Compare.Value)
|
|
||||||
result.testResult = a <= b
|
|
||||||
|
|
||||||
case "has":
|
|
||||||
result.testResult = strings.Contains(flagVal, t.Compare.Value)
|
|
||||||
|
|
||||||
case "nothave":
|
|
||||||
result.testResult = !strings.Contains(flagVal, t.Compare.Value)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result.testResult = isset
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
notset := !match
|
|
||||||
result.testResult = notset
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type tests struct {
|
|
||||||
TestItems []*testItem `yaml:"test_items"`
|
|
||||||
BinOp binOp `yaml:"bin_op"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tests) execute(s string) *testOutput {
|
|
||||||
finalOutput := &testOutput{}
|
|
||||||
|
|
||||||
res := make([]testOutput, len(ts.TestItems))
|
|
||||||
if len(res) == 0 {
|
|
||||||
return finalOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, t := range ts.TestItems {
|
|
||||||
res[i] = *(t.execute(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
var result bool
|
|
||||||
// If no binary operation is specified, default to AND
|
|
||||||
switch ts.BinOp {
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "unknown binary operator for tests %s\n", ts.BinOp)
|
|
||||||
os.Exit(1)
|
|
||||||
case and, "":
|
|
||||||
result = true
|
|
||||||
for i := range res {
|
|
||||||
result = result && res[i].testResult
|
|
||||||
}
|
|
||||||
case or:
|
|
||||||
result = false
|
|
||||||
for i := range res {
|
|
||||||
result = result || res[i].testResult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finalOutput.testResult = result
|
|
||||||
finalOutput.actualResult = res[0].actualResult
|
|
||||||
|
|
||||||
return finalOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
func toNumeric(a, b string) (c, d int) {
|
|
||||||
var err error
|
|
||||||
c, err = strconv.Atoi(a)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error converting %s: %s\n", a, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
d, err = strconv.Atoi(b)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error converting %s: %s\n", b, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, d
|
|
||||||
}
|
|
@ -1,121 +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 check
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
in []byte
|
|
||||||
controls *Controls
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var err error
|
|
||||||
in, err = ioutil.ReadFile("data")
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed reading test data: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// substitute variables in data file
|
|
||||||
user := os.Getenv("USER")
|
|
||||||
s := strings.Replace(string(in), "$user", user, -1)
|
|
||||||
|
|
||||||
controls, err = NewControls(MASTER, []byte(s))
|
|
||||||
// controls, err = NewControls(MASTER, in)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed creating test controls: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTestExecute(t *testing.T) {
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
*Check
|
|
||||||
str string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[0],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --allow-privileged=false --option1=20,30,40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[1],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --allow-privileged=false",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[2],
|
|
||||||
"niinai 13617 2635 99 19:26 pts/20 00:03:08 ./kube-apiserver --insecure-port=0 --anonymous-auth",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[3],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --secure-port=0 --audit-log-maxage=40 --option",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[4],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --max-backlog=20 --secure-port=0 --audit-log-maxage=40 --option",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[5],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[6],
|
|
||||||
"2:45 .. --kubelet-clientkey=foo --kubelet-client-certificate=bar --admission-control=Webhook,RBAC",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[7],
|
|
||||||
"2:45 .. --secure-port=0 --kubelet-client-certificate=bar --admission-control=Webhook,RBAC",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[8],
|
|
||||||
"644",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[9],
|
|
||||||
"640",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[9],
|
|
||||||
"600",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[10],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[11],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,RBAC ---audit-log-maxage=40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[12],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --option --admission-control=WebHook,Something,RBAC ---audit-log-maxage=40",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
controls.Groups[0].Checks[13],
|
|
||||||
"2:45 ../kubernetes/kube-apiserver --option --admission-control=Something ---audit-log-maxage=40",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
res := c.Tests.execute(c.str).testResult
|
|
||||||
if !res {
|
|
||||||
t.Errorf("%s, expected:%v, got:%v\n", c.Text, true, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user