diff --git a/pkg/cli/server.go b/pkg/cli/server.go index f7dcc5ca0..a0505480c 100644 --- a/pkg/cli/server.go +++ b/pkg/cli/server.go @@ -46,7 +46,7 @@ Run Terrascan as an API server that inspects incoming IaC (Infrastructure-as-Cod } func server(cmd *cobra.Command, args []string) { - httpserver.Start(port, ConfigFile, certFile, privateKeyFile) + httpserver.Start(port, certFile, privateKeyFile) } func init() { diff --git a/pkg/config/global.go b/pkg/config/global.go index 14e590c92..f82f75b69 100644 --- a/pkg/config/global.go +++ b/pkg/config/global.go @@ -41,6 +41,8 @@ var ( // in configFile will get default values func LoadGlobalConfig(configFile string) error { // Start with the defaults + global = &TerrascanConfig{} + global.Policy = Policy{ BasePath: defaultBasePolicyPath, RepoPath: defaultPolicyRepoPath, @@ -120,50 +122,80 @@ func LoadGlobalConfig(configFile string) error { // GetPolicyBasePath returns the configured policy base path func GetPolicyBasePath() string { + if global == nil { + return defaultBasePolicyPath + } return global.Policy.BasePath } // GetPolicyRepoPath return the configured path to the policies repo locally downloaded func GetPolicyRepoPath() string { + if global == nil { + return defaultPolicyRepoPath + } return global.Policy.RepoPath } // GetPolicyRepoURL returns the configured policy repo url func GetPolicyRepoURL() string { + if global == nil { + return defaultPolicyRepoURL + } return global.Policy.RepoURL } // GetPolicyBranch returns the configured policy repo url func GetPolicyBranch() string { + if global == nil { + return defaultPolicyBranch + } return global.Policy.Branch } // GetScanRules returns the configured scan rules func GetScanRules() []string { + if global == nil { + return nil + } return global.Rules.ScanRules } // GetSkipRules returns the configured skips rules func GetSkipRules() []string { + if global == nil { + return nil + } return global.Rules.SkipRules } // GetSeverityLevel returns the configured severity level func GetSeverityLevel() string { + if global == nil { + return "" + } return global.Severity.Level } // GetCategoryList returns the configured list of category of violations func GetCategoryList() []string { + if global == nil { + return nil + } return global.Category.List } // GetNotifications returns the configured notifier map func GetNotifications() map[string]Notifier { + if global == nil { + return nil + } return global.Notifications } // GetK8sAdmissionControl returns kubernetes admission control configuration func GetK8sAdmissionControl() K8sAdmissionControl { + if global == nil { + return K8sAdmissionControl{} + } return global.K8sAdmissionControl } diff --git a/pkg/config/types.go b/pkg/config/types.go index fa56bfaba..c5853a6f8 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -17,7 +17,7 @@ package config // Global initalizes GlobalConfig struct -var global *TerrascanConfig = &TerrascanConfig{} +var global *TerrascanConfig // TerrascanConfig struct defines global variables/configurations across terrascan type TerrascanConfig struct { diff --git a/pkg/http-server/handler.go b/pkg/http-server/handler.go index 41fc52691..eb86e5a90 100644 --- a/pkg/http-server/handler.go +++ b/pkg/http-server/handler.go @@ -18,13 +18,10 @@ package httpserver // APIHandler struct for http api server type APIHandler struct { - test bool - configFile string + test bool } // NewAPIHandler returns a new APIHandler{} -func NewAPIHandler(configFile string) *APIHandler { - return &APIHandler{ - configFile: configFile, - } +func NewAPIHandler() *APIHandler { + return &APIHandler{} } diff --git a/pkg/http-server/handler_test.go b/pkg/http-server/handler_test.go index dd394336b..15c926eb1 100644 --- a/pkg/http-server/handler_test.go +++ b/pkg/http-server/handler_test.go @@ -8,10 +8,8 @@ import ( func TestNewAPIHandler(t *testing.T) { t.Run("new API gateway", func(t *testing.T) { var ( - want = APIHandler{ - configFile: "", - } - got = NewAPIHandler("") + want = APIHandler{} + got = NewAPIHandler() ) if !reflect.DeepEqual(*got, want) { t.Errorf("got: '%v', want: '%v'", *got, want) diff --git a/pkg/http-server/health_test.go b/pkg/http-server/health_test.go index 2a447ea47..08b92f8a8 100644 --- a/pkg/http-server/health_test.go +++ b/pkg/http-server/health_test.go @@ -8,7 +8,7 @@ import ( func TestHealth(t *testing.T) { - handler := NewAPIHandler("") + handler := NewAPIHandler() t.Run("test health api", func(t *testing.T) { var ( diff --git a/pkg/http-server/k8s_testdata/config-dashboard-true.toml b/pkg/http-server/k8s_testdata/config-dashboard-true.toml new file mode 100644 index 000000000..82c3356fa --- /dev/null +++ b/pkg/http-server/k8s_testdata/config-dashboard-true.toml @@ -0,0 +1,2 @@ +[k8s-admission-control] + dashboard = true \ No newline at end of file diff --git a/pkg/http-server/k8s_testdata/config-with-dashboard-deny-categories.toml b/pkg/http-server/k8s_testdata/config-with-dashboard-deny-categories.toml new file mode 100644 index 000000000..d29e631a6 --- /dev/null +++ b/pkg/http-server/k8s_testdata/config-with-dashboard-deny-categories.toml @@ -0,0 +1,6 @@ +[k8s-admission-control] + dashboard = true + denied-categories = [ + "Identity and Access Management", + "Network Security", + ] \ No newline at end of file diff --git a/pkg/http-server/routes.go b/pkg/http-server/routes.go index ae63054bd..88791c452 100644 --- a/pkg/http-server/routes.go +++ b/pkg/http-server/routes.go @@ -29,8 +29,8 @@ type Route struct { // Routes returns a slice of routes of API endpoints to be registered with // http server -func (g *APIServer) Routes(configFile string) []*Route { - h := NewAPIHandler(configFile) +func (g *APIServer) Routes() []*Route { + h := NewAPIHandler() routes := []*Route{ {verb: "GET", path: "/health", fn: h.Health}, {verb: "POST", path: versionedPath("/{iac}/{iacVersion}/{cloud}/local/file/scan"), fn: h.scanFile}, diff --git a/pkg/http-server/routes_test.go b/pkg/http-server/routes_test.go index 3e2058828..06d3dadab 100644 --- a/pkg/http-server/routes_test.go +++ b/pkg/http-server/routes_test.go @@ -8,7 +8,7 @@ func TestRoutes(t *testing.T) { t.Run("health route check", func(t *testing.T) { var ( server = NewAPIServer() - got = server.Routes("") + got = server.Routes() passed = false ) diff --git a/pkg/http-server/start.go b/pkg/http-server/start.go index bf9199ade..93e16863b 100644 --- a/pkg/http-server/start.go +++ b/pkg/http-server/start.go @@ -28,13 +28,13 @@ import ( ) // Start initializes api routes and starts http server -func Start(port, configFile, certFile, privateKeyFile string) { +func Start(port, certFile, privateKeyFile string) { // create a new API server server := NewAPIServer() // get all routes - routes := server.Routes(configFile) + routes := server.Routes() // get port if port == GatewayDefaultPort && os.Getenv(TerrascanServerPort) != "" { diff --git a/pkg/http-server/webhook-scan-logs.go b/pkg/http-server/webhook-scan-logs.go index d4733aefd..7e8d4e833 100644 --- a/pkg/http-server/webhook-scan-logs.go +++ b/pkg/http-server/webhook-scan-logs.go @@ -81,7 +81,7 @@ func (g *APIHandler) getLogs(w http.ResponseWriter, r *http.Request) { ) // Validate if authorized (API key is specified and matched the server one (saved in an environment variable) - validatingWebhook := admissionWebhook.NewValidatingWebhook(g.configFile, []byte("")) + validatingWebhook := admissionWebhook.NewValidatingWebhook([]byte("")) if err := validatingWebhook.Authorize(apiKey); err != nil { switch err { case admissionWebhook.ErrAPIKeyMissing: diff --git a/pkg/http-server/webhook-scan.go b/pkg/http-server/webhook-scan.go index ff88b7fc1..b210b9054 100644 --- a/pkg/http-server/webhook-scan.go +++ b/pkg/http-server/webhook-scan.go @@ -46,7 +46,7 @@ func (g *APIHandler) validateK8SWebhook(w http.ResponseWriter, r *http.Request) } zap.S().Debugf("scanning configuration webhook request: %+v", string(body)) - validatingWebhook := admissionWebhook.NewValidatingWebhook(g.configFile, body) + validatingWebhook := admissionWebhook.NewValidatingWebhook(body) // Validate if authorized (API key is specified and matched the server one (saved in an environment variable) if err := validatingWebhook.Authorize(apiKey); err != nil { switch err { diff --git a/pkg/http-server/webhook-scan_test.go b/pkg/http-server/webhook-scan_test.go index 0864c9379..6229f5816 100644 --- a/pkg/http-server/webhook-scan_test.go +++ b/pkg/http-server/webhook-scan_test.go @@ -9,8 +9,10 @@ import ( "net/http/httptest" "os" "path/filepath" + "strings" "testing" + "github.com/accurics/terrascan/pkg/config" "github.com/accurics/terrascan/pkg/k8s/dblogs" "github.com/gorilla/mux" v1 "k8s.io/api/admission/v1" @@ -34,6 +36,7 @@ func TestUWebhooks(t *testing.T) { allowed bool statusCode int32 statusMessage bool + dashboardCheck bool }{ { name: "missing api key", @@ -59,14 +62,6 @@ func TestUWebhooks(t *testing.T) { wantStatus: http.StatusUnauthorized, configFile: testConfigFile, }, - { - name: "invalid api key", - contentRequestPath: testFilePath, - apiKey: testAPIKey, - envAPIKey: "Invalid API KEY", - wantStatus: http.StatusUnauthorized, - configFile: testConfigFile, - }, { name: "invalid request json content", contentRequestPath: filepath.Join(k8sTestData, "invalid.json"), @@ -167,10 +162,39 @@ func TestUWebhooks(t *testing.T) { allowed: true, wantStatus: http.StatusOK, }, + { + name: "risky request object with dashboard true", + contentRequestPath: filepath.Join(k8sTestData, "risky_testconfig.json"), + apiKey: testAPIKey, + envAPIKey: testEnvAPIKey, + configFile: filepath.Join(k8sTestData, "config-dashboard-true.toml"), + warnings: true, + allowed: true, + wantStatus: http.StatusOK, + dashboardCheck: true, + }, + { + name: "risky request object with denied categories and dashboard true", + contentRequestPath: filepath.Join(k8sTestData, "risky_testconfig.json"), + apiKey: testAPIKey, + envAPIKey: testEnvAPIKey, + configFile: filepath.Join(k8sTestData, "config-with-dashboard-deny-categories.toml"), + warnings: false, + allowed: false, + statusCode: 403, + statusMessage: true, + wantStatus: http.StatusOK, + dashboardCheck: true, + }, } for _, tt := range table { t.Run(tt.name, func(t *testing.T) { + err := config.LoadGlobalConfig(tt.configFile) + if err != nil { + t.Errorf("error while loading the config file '%s', error: %v", tt.configFile, err) + } + os.Setenv("K8S_WEBHOOK_API_KEY", tt.envAPIKey) // test file to upload @@ -206,7 +230,7 @@ func TestUWebhooks(t *testing.T) { }) res := httptest.NewRecorder() // new api handler - h := &APIHandler{test: true, configFile: tt.configFile} + h := &APIHandler{test: true} h.validateK8SWebhook(res, req) if res.Code != tt.wantStatus { @@ -229,11 +253,7 @@ func TestUWebhooks(t *testing.T) { t.Errorf("mismatch Status code Got: %v, expected: %v", response.Response.Result.Code, tt.statusCode) } - // TODO: this needs to improved and more tests added after the config file changes - // commenting the log message check for now, it can be fixed later - // making the blind mode default has changed the log message output - - /* + if tt.dashboardCheck { if tt.warnings || tt.statusMessage { var logPath string if tt.warnings { @@ -249,7 +269,7 @@ func TestUWebhooks(t *testing.T) { t.Errorf("mismatch Log path. Got: %v, expected: %v", logPath, expectedLogPath) } } - */ + } } }) } diff --git a/pkg/k8s/admission-webhook/validating-webhook.go b/pkg/k8s/admission-webhook/validating-webhook.go index 3658b278e..4572fbc43 100644 --- a/pkg/k8s/admission-webhook/validating-webhook.go +++ b/pkg/k8s/admission-webhook/validating-webhook.go @@ -42,15 +42,13 @@ import ( // the kubernetes API server and decides whether the admission request from // the kubernetes client should be allowed or not type ValidatingWebhook struct { - configFile string requestBody []byte dblogger *dblogs.WebhookScanLogger } // NewValidatingWebhook returns a new, empty ValidatingWebhook struct -func NewValidatingWebhook(configFile string, body []byte) AdmissionWebhook { +func NewValidatingWebhook(body []byte) AdmissionWebhook { return ValidatingWebhook{ - configFile: configFile, dblogger: dblogs.NewWebhookScanLogger(), requestBody: body, } @@ -162,7 +160,7 @@ func (w ValidatingWebhook) ProcessWebhook(review admissionv1.AdmissionReview, se return w.createResponseAdmissionReview(review, allowed, output, logMsg), fmt.Errorf(errMsg, msg, err) } - // Calculate if there are anydeny violations + // Calculate if there are any deny violations denyViolations, err = w.getDenyViolations(output) if err != nil { msg := "failed to figure out denied violations" @@ -215,13 +213,7 @@ func (w ValidatingWebhook) scanK8sFile(filePath string) (runtime.Output, error) func (w ValidatingWebhook) getDenyViolations(output runtime.Output) ([]results.Violation, error) { // Calcualte the deny violations according to the configuration specified in the config file - configReader, err := config.NewTerrascanConfigReader(w.configFile) - if err != nil { - zap.S().Errorf("error loading config file: '%v'", err) - return nil, err - } - - denyViolations := w.getDeniedViolations(*output.Violations.ViolationStore, configReader.GetK8sAdmissionControl()) + denyViolations := w.getDeniedViolations(*output.Violations.ViolationStore, config.GetK8sAdmissionControl()) return denyViolations, nil } diff --git a/pkg/notifications/notifiers_test.go b/pkg/notifications/notifiers_test.go index 0ba6602c2..95d5d5893 100644 --- a/pkg/notifications/notifiers_test.go +++ b/pkg/notifications/notifiers_test.go @@ -89,7 +89,7 @@ func TestNewNotifiers(t *testing.T) { }, { name: "no config file specified", - wantErr: nil, + wantErr: ErrNotificationNotPresent, }, }