mirror of
https://github.com/aquasecurity/linux-bench.git
synced 2025-02-22 06:05:33 +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