mirror of
https://github.com/aquasecurity/linux-bench.git
synced 2025-02-22 14:15:32 +01:00
Initialized basic bench project
This commit is contained in:
commit
958fbe7666
7 changed files with 2008 additions and 0 deletions
40
README.md
Normal file
40
README.md
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
[](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
137
app.go
Normal 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
59
app_test.go
Normal 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
1530
cfg/1.1.0/definitions.yaml
Normal file
File diff suppressed because it is too large
Load diff
19
main.go
Normal file
19
main.go
Normal 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
105
root.go
Normal 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
118
utils.go
Normal 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 ""
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue