Initialized basic bench project

This commit is contained in:
Yoav Hizkiahou 2019-01-27 16:12:29 +02:00
commit 958fbe7666
7 changed files with 2008 additions and 0 deletions

40
README.md Normal file
View file

@ -0,0 +1,40 @@
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
Linux-bench is a Go application that checks whether The linux operating system is configured securely by running the checks documented in the CIS Linux Benchmark.
Tests are configured with YAML files, making this tool easy to update as test specifications evolve.
## CIS Linux Benchmark support
linux-bench currently supports tests for multiple platforms of Linux (ubntu, rhel and debian).
linux-bench will determine the test set to run based on the operating system and the boot loader running on the host machine.
## Installation
### Installing from sources
Intall [Go](https://golang.org/doc/install), then
clone this repository and run as follows (assuming your [$GOPATH is set](https://github.com/golang/go/wiki/GOPATH)):
```shell
go get github.com/aquasecurity/linux-bench
cd $GOPATH/src/github.com/aquasecurity/linux-bench
go build -o linux-bench .
# See all supported options
./linux-bench --help
# Run checks
./linux-bench
# Run checks for specified linux cis version
./linux-bench
```
# Tests
Tests are specified in definition files `cfg/<version>/definitions.yaml.
Where `<version>` is the version of linux cis for which the test applies.
# Contributing
We welcome PRs and issue reports.

137
app.go Normal file
View file

@ -0,0 +1,137 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/aquasecurity/bench-common/check"
"github.com/aquasecurity/bench-common/util"
"github.com/golang/glog"
"github.com/spf13/cobra"
)
func app(cmd *cobra.Command, args []string) {
var version string
var err error
if linuxCisVersion != "" {
version = linuxCisVersion
} else {
version = "1.1.0"
}
path, err := getDefinitionFilePath(version)
if err != nil {
util.ExitWithError(err)
}
constraints, err := getConstraints()
if err != nil {
util.ExitWithError(err)
}
controls, err := getControls(path, constraints)
if err != nil {
util.ExitWithError(err)
}
summary := runControls(controls, checkList)
err = outputResults(controls, summary)
if err != nil {
util.ExitWithError(err)
}
}
func outputResults(controls *check.Controls, summary check.Summary) error {
// 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 {
return err
}
fmt.Println(string(out))
} else {
util.PrettyPrint(controls, summary, noRemediations, includeTestOutput)
}
return nil
}
func runControls(controls *check.Controls, checkList string) check.Summary {
var summary check.Summary
if checkList != "" {
ids := util.CleanIDs(checkList)
summary = controls.RunChecks(ids...)
} else {
summary = controls.RunGroup()
}
return summary
}
func getControls(path string, constraints []string) (*check.Controls, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
controls, err := check.NewControls([]byte(data), constraints)
if err != nil {
return nil, err
}
return controls, err
}
func getDefinitionFilePath(version string) (string, error) {
filename := "definitions.yaml"
glog.V(2).Info(fmt.Sprintf("Looking for config for version %s", version))
path := filepath.Join(cfgDir, version)
file := filepath.Join(path, filename)
glog.V(2).Info(fmt.Sprintf("Looking for config file: %s\n", file))
_, err := os.Stat(file)
if err != nil {
return "", err
}
return file, nil
}
func getConstraints() (constraints []string, err error) {
platform, err := GetOperatingSystem()
if err != nil {
glog.V(1).Info(fmt.Sprintf("Failed to get operating system platform, %s", err))
}
boot, err := GetBootLoader()
if err != nil {
glog.V(1).Info(fmt.Sprintf("Failed to get boot loader, %s", err))
}
syslog, err := GetSystemLogManager()
if err != nil {
glog.V(1).Info(fmt.Sprintf("Failed to get syslog tool, %s", err))
}
lsm, err := GetLSM()
if err != nil {
glog.V(1).Info(fmt.Sprintf("Failed to get lsm, %s", err))
}
constraints = append(constraints,
fmt.Sprintf("platform=%s", platform),
fmt.Sprintf("boot=%s", boot),
fmt.Sprintf("syslog=%s", syslog),
fmt.Sprintf("lsm=%s", lsm),
)
return constraints, nil
}

59
app_test.go Normal file
View file

@ -0,0 +1,59 @@
package main
import (
"os"
"testing"
)
var (
cfgdir = "./cfg"
ver = "1.1.0"
path string
)
// Tests all standard linux-bench defintion files
func TestGetDefinitionFilePath(t *testing.T) {
d, err := os.Open(cfgdir)
if err != nil {
t.Errorf("unexpected error: %s\n", err)
}
vers, err := d.Readdirnames(-1)
if err != nil {
t.Errorf("unexpected error: %s\n", err)
}
for _, ver := range vers {
_, err := getDefinitionFilePath(ver)
if err != nil {
t.Errorf("unexpected error: %s\n", err)
}
}
}
func TestGetControls(t *testing.T) {
var err error
path, err = getDefinitionFilePath(ver)
if err != nil {
t.Errorf("unexpected error: %s\n", err)
}
_, err = getControls(path, nil)
if err != nil {
t.Errorf("unexpected error: %s\n", err)
}
}
func TestRunControls(t *testing.T) {
control, err := getControls(path, nil)
if err != nil {
t.Errorf("unexpected error: %s\n", err)
}
// Run all checks
_ = runControls(control, "")
// Run only specified checks
checkList := "1.2, 2.1"
_ = runControls(control, checkList)
}

1530
cfg/1.1.0/definitions.yaml Normal file

File diff suppressed because it is too large Load diff

19
main.go Normal file
View file

@ -0,0 +1,19 @@
// 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
func main() {
Execute()
}

105
root.go Normal file
View file

@ -0,0 +1,105 @@
// 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"
"os"
goflag "flag"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
noResults bool
noSummary bool
noRemediations bool
linuxCisVersion string
cfgDir string
cfgFile string
checkList string
jsonFmt bool
includeTestOutput bool
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "linux-bench",
Short: "linux-bench is a Go application that checks whether the linux operating system is deployed securely",
Long: `This tool runs the CIS Linux Benchmark (https://www.cisecurity.org/benchmark/linux/)`,
Run: app,
}
// 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)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags, which, if defined here,
// will be global for your application.
// RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.linux-bench.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
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.Flags().StringVarP(&linuxCisVersion, "version", "", "1.1.0", "Specify linux cis version, automatically detected if unset")
RootCmd.Flags().StringVarP(&cfgDir, "config-dir", "D", "cfg", "directory to get benchmark definitions")
RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
RootCmd.PersistentFlags().BoolVar(&includeTestOutput, "include-test-output", false, "Prints the test's output")
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"`,
)
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)
}
viper.SetConfigName(".linux-bench") // name of config file (without extension)
viper.AddConfigPath("$HOME") // adding home directory as first search path
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}

118
utils.go Normal file
View file

@ -0,0 +1,118 @@
package main
import (
"os/exec"
"regexp"
"strings"
)
func GetOperatingSystem() (platform string, err error) {
out, err := exec.Command("bash", "-c", "cat /etc/os-release").Output()
if err != nil {
return "", err
} else {
output := strings.ToLower(string(out))
output = strings.Replace(output, `"`, "", -1)
output = strings.Replace(output, `_id`, "", -1) // version_id kills the regex
flagRe := regexp.MustCompile("id" + `=([^ \n]*)`)
vals := flagRe.FindStringSubmatch(output)
if len(vals) > 1 {
platform = vals[1]
}
platform += getPlatformVersion(output, platform)
}
return platform, nil
}
func GetBootLoader() (boot string, err error) {
out, err := exec.Command("grub-install", "--version").Output()
if err != nil {
out, err = exec.Command("bash", "-c", "ls /boot | grep grub").Output()
if err != nil {
out, err = exec.Command("bash", "-c", "ls /boot/boot | grep grub").Output()
if err != nil {
return "", err
}
}
}
output := strings.ToLower(string(out))
if strings.Contains(output, "grub2") {
boot = "grub2"
} else if strings.Contains(output, "grub") {
boot = "grub"
}
return boot, nil
}
func GetSystemLogManager() (syslog string, err error) {
out, err := exec.Command("bash", "-c", "sudo lsof | grep /var/log/syslog | cut -f1 -d' '").Output()
if err != nil {
out, err := exec.Command("bash", "-c", "service rsyslog status").Output()
if err != nil {
return "", err
}
output := strings.ToLower(string(out))
if strings.Contains(output, "active (running)") {
syslog = "rsyslog"
} else {
syslog = "syslog-ng"
}
} else {
output := strings.ToLower(string(out))
if strings.Contains(output, "syslog-ng") {
syslog = "syslog-ng"
} else {
syslog = "rsyslog"
}
}
return syslog, nil
}
func GetLSM() (lsm string, err error) {
out, err := exec.Command("bash", "-c", "sudo apparmor_status").Output()
if err != nil {
out, err = exec.Command("bash", "-c", "sestatus").Output()
if err != nil {
return "", err
} else {
output := strings.ToLower(string(out))
space := regexp.MustCompile(`\s+`)
output = space.ReplaceAllString(output, " ")
if strings.Contains(output, "selinux status: enabled") {
lsm = "selinux"
}
}
} else {
output := strings.ToLower(string(out))
if strings.Contains(output, "apparmor module is loaded") {
lsm = "apparmor"
}
}
return lsm, nil
}
func getPlatformVersion(output, platform string) string {
flagRe := regexp.MustCompile("version_id" + `=([^ \n]*)`)
vals := flagRe.FindStringSubmatch(output)
if len(vals) > 1 {
switch platform {
case "rhel":
return vals[1][:1] // Get the major version only, examaple: 7.6 will return 7
default:
return ""
}
}
return ""
}