Add audit mode to CLI

Add option to send audit results to a remote host

add audit flag to print results to stdout

add comments

make comments more consistent

move audit test

fix fullaudit_test

add test instructions to README

update audit test

simplify stdout output

update comment

fix import

run audit by default
This commit is contained in:
Bobby Brennan
2019-03-28 17:06:44 +00:00
parent 6b6799ffd9
commit e4dd53d1c0
6 changed files with 139 additions and 85 deletions

View File

@@ -39,3 +39,10 @@ helm upgrade --install fairwinds charts/fairwinds/ --namespace fairwinds --recre
kubectl port-forward --namespace fairwinds svc/fairwinds-fairwinds-dashboard 8080:80 &
open http://localhost:8080
```
## Run tests
```
go list ./ | grep -v vendor | xargs golint -set_exit_status
go list ./ | grep -v vendor | xargs go vet
go test ./pkg/... -v -coverprofile cover.out
```

55
main.go
View File

@@ -15,6 +15,8 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
@@ -26,7 +28,9 @@ import (
conf "github.com/reactiveops/fairwinds/pkg/config"
"github.com/reactiveops/fairwinds/pkg/dashboard"
"github.com/reactiveops/fairwinds/pkg/kube"
"github.com/reactiveops/fairwinds/pkg/validator"
fwebhook "github.com/reactiveops/fairwinds/pkg/webhook"
"gopkg.in/yaml.v2"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apitypes "k8s.io/apimachinery/pkg/types"
@@ -43,8 +47,10 @@ var log = logf.Log.WithName("fairwinds")
func main() {
dashboard := flag.Bool("dashboard", false, "Runs the webserver for Fairwinds dashboard.")
webhook := flag.Bool("webhook", false, "Runs the webhook webserver.")
audit := flag.Bool("audit", false, "Runs a one-time audit.")
dashboardPort := flag.Int("dashboard-port", 8080, "Port for the dashboard webserver")
webhookPort := flag.Int("webhook-port", 9876, "Port for the webhook webserver")
auditDestination := flag.String("audit-destination", "", "Destination URL to send audit results (prints to stdout if unspecified)")
var disableWebhookConfigInstaller bool
flag.BoolVar(&disableWebhookConfigInstaller, "disable-webhook-config-installer", false,
@@ -58,17 +64,16 @@ func main() {
os.Exit(1)
}
if !*dashboard && !*webhook && !*audit {
*audit = true
}
if *webhook {
startWebhookServer(c, disableWebhookConfigInstaller, *webhookPort)
}
if *dashboard {
} else if *dashboard {
startDashboardServer(c, *dashboardPort)
}
if !*dashboard && !*webhook {
glog.Println("Must specify either -webhook, -dashboard, or both")
os.Exit(1)
} else if *audit {
runAudit(c, *auditDestination)
}
}
@@ -154,3 +159,37 @@ func startWebhookServer(c conf.Configuration, disableWebhookConfigInstaller bool
os.Exit(1)
}
}
func runAudit(c conf.Configuration, destination string) {
k, _ := kube.CreateKubeAPI()
auditData, err := validator.RunAudit(c, k)
if err != nil {
panic(err)
}
if destination != "" {
jsonData, err := json.Marshal(auditData)
if err != nil {
panic(err)
}
req, err := http.NewRequest("POST", destination, bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
glog.Println(string(body))
} else {
y, err := yaml.Marshal(auditData)
if err != nil {
panic(err)
}
fmt.Println(string(y))
}
}

View File

@@ -18,15 +18,9 @@ const (
TemplateFile = "pkg/dashboard/templates/" + TemplateName
)
// TemplateData represents data in a format that's template friendly.
type TemplateData struct {
ClusterSummary *validator.ResultSummary
NamespacedResults validator.NamespacedResults
}
// MainHandler gets template data and renders the dashboard with it.
func MainHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, kubeAPI *kube.API) {
templateData, err := getTemplateData(c, kubeAPI)
templateData, err := validator.RunAudit(c, kubeAPI)
if err != nil {
http.Error(w, "Error Fetching Deployments", 500)
return
@@ -62,7 +56,7 @@ func MainHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, k
// EndpointHandler gets template data and renders json with it.
func EndpointHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, kubeAPI *kube.API) {
templateData, err := getTemplateData(c, kubeAPI)
templateData, err := validator.RunAudit(c, kubeAPI)
if err != nil {
http.Error(w, "Error Fetching Deployments", 500)
return
@@ -72,36 +66,3 @@ func EndpointHandler(w http.ResponseWriter, r *http.Request, c conf.Configuratio
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(templateData)
}
func getTemplateData(config conf.Configuration, kubeAPI *kube.API) (TemplateData, error) {
// TODO: Once we are validating more than deployments,
// we will need to merge the namespaceResults that get returned
// from each validation.
nsResults, err := validator.ValidateDeploys(config, kubeAPI)
if err != nil {
return TemplateData{}, err
}
var clusterSuccesses, clusterErrors, clusterWarnings uint
// Aggregate all summary counts to get a clusterwide count.
for _, nsRes := range nsResults {
for _, rr := range nsRes.Results {
clusterErrors += rr.Summary.Errors
clusterWarnings += rr.Summary.Warnings
clusterSuccesses += rr.Summary.Successes
}
}
templateData := TemplateData{
ClusterSummary: &validator.ResultSummary{
Errors: clusterErrors,
Warnings: clusterWarnings,
Successes: clusterSuccesses,
},
NamespacedResults: nsResults,
}
return templateData, nil
}

View File

@@ -1,36 +0,0 @@
package dashboard
import (
"testing"
conf "github.com/reactiveops/fairwinds/pkg/config"
"github.com/reactiveops/fairwinds/pkg/validator"
"github.com/reactiveops/fairwinds/test"
"github.com/stretchr/testify/assert"
)
func TestGetTemplateData(t *testing.T) {
k8s := test.SetupTestAPI()
k8s = test.SetupAddDeploys(k8s, "test")
c := conf.Configuration{
HealthChecks: conf.HealthChecks{
ReadinessProbeMissing: conf.SeverityError,
LivenessProbeMissing: conf.SeverityWarning,
},
}
sum := &validator.ResultSummary{
Successes: uint(4),
Warnings: uint(1),
Errors: uint(1),
}
actualTmplData, _ := getTemplateData(c, k8s)
assert.EqualValues(t, actualTmplData.ClusterSummary, sum)
assert.Equal(t, len(actualTmplData.NamespacedResults["test"].Results), 1, "should be equal")
assert.Equal(t, len(actualTmplData.NamespacedResults["test"].Results[0].PodResults), 1, "should be equal")
assert.Equal(t, len(actualTmplData.NamespacedResults["test"].Results[0].PodResults[0].ContainerResults), 1, "should be equal")
assert.Equal(t, len(actualTmplData.NamespacedResults["test"].Results[0].PodResults[0].ContainerResults[0].Messages), 6, "should be equal")
}

View File

@@ -0,0 +1,47 @@
package validator
import (
conf "github.com/reactiveops/fairwinds/pkg/config"
"github.com/reactiveops/fairwinds/pkg/kube"
)
// AuditData contains all the data from a full Fairwinds audit
type AuditData struct {
ClusterSummary ResultSummary
NamespacedResults NamespacedResults
}
// RunAudit runs a full Fairwinds audit and returns an AuditData object
func RunAudit(config conf.Configuration, kubeAPI *kube.API) (AuditData, error) {
// TODO: Validate StatefulSets, DaemonSets, Cron jobs
// in addition to deployments
// TODO: Once we are validating more than deployments,
// we will need to merge the namespaceResults that get returned
// from each validation.
nsResults, err := ValidateDeploys(config, kubeAPI)
if err != nil {
return AuditData{}, err
}
var clusterSuccesses, clusterErrors, clusterWarnings uint
// Aggregate all summary counts to get a clusterwide count.
for _, nsRes := range nsResults {
for _, rr := range nsRes.Results {
clusterErrors += rr.Summary.Errors
clusterWarnings += rr.Summary.Warnings
clusterSuccesses += rr.Summary.Successes
}
}
auditData := AuditData{
ClusterSummary: ResultSummary{
Errors: clusterErrors,
Warnings: clusterWarnings,
Successes: clusterSuccesses,
},
NamespacedResults: nsResults,
}
return auditData, nil
}

View File

@@ -0,0 +1,36 @@
package validator
import (
"testing"
conf "github.com/reactiveops/fairwinds/pkg/config"
"github.com/reactiveops/fairwinds/test"
"github.com/stretchr/testify/assert"
)
func TestGetTemplateData(t *testing.T) {
k8s := test.SetupTestAPI()
k8s = test.SetupAddDeploys(k8s, "test")
c := conf.Configuration{
HealthChecks: conf.HealthChecks{
ReadinessProbeMissing: conf.SeverityError,
LivenessProbeMissing: conf.SeverityWarning,
},
}
sum := ResultSummary{
Successes: uint(4),
Warnings: uint(1),
Errors: uint(1),
}
actualAudit, err := RunAudit(c, k8s)
assert.Equal(t, err, nil, "error should be nil")
assert.EqualValues(t, actualAudit.ClusterSummary, sum)
assert.Equal(t, len(actualAudit.NamespacedResults["test"].Results), 1, "should be equal")
assert.Equal(t, len(actualAudit.NamespacedResults["test"].Results[0].PodResults), 1, "should be equal")
assert.Equal(t, len(actualAudit.NamespacedResults["test"].Results[0].PodResults[0].ContainerResults), 1, "should be equal")
assert.Equal(t, len(actualAudit.NamespacedResults["test"].Results[0].PodResults[0].ContainerResults[0].Messages), 6, "should be equal")
}