Skip to content

Commit

Permalink
config file changes for terrascan server (#780)
Browse files Browse the repository at this point in the history
1. fix global config
2. fix and add tests
  • Loading branch information
patilpankaj212 committed May 17, 2021
1 parent 72e3ebc commit a3f26c1
Show file tree
Hide file tree
Showing 16 changed files with 94 additions and 47 deletions.
2 changes: 1 addition & 1 deletion pkg/cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
32 changes: 32 additions & 0 deletions pkg/config/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 3 additions & 6 deletions pkg/http-server/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
}
6 changes: 2 additions & 4 deletions pkg/http-server/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pkg/http-server/health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

func TestHealth(t *testing.T) {

handler := NewAPIHandler("")
handler := NewAPIHandler()

t.Run("test health api", func(t *testing.T) {
var (
Expand Down
2 changes: 2 additions & 0 deletions pkg/http-server/k8s_testdata/config-dashboard-true.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[k8s-admission-control]
dashboard = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[k8s-admission-control]
dashboard = true
denied-categories = [
"Identity and Access Management",
"Network Security",
]
4 changes: 2 additions & 2 deletions pkg/http-server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
2 changes: 1 addition & 1 deletion pkg/http-server/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
4 changes: 2 additions & 2 deletions pkg/http-server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) != "" {
Expand Down
2 changes: 1 addition & 1 deletion pkg/http-server/webhook-scan-logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pkg/http-server/webhook-scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
50 changes: 35 additions & 15 deletions pkg/http-server/webhook-scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -34,6 +36,7 @@ func TestUWebhooks(t *testing.T) {
allowed bool
statusCode int32
statusMessage bool
dashboardCheck bool
}{
{
name: "missing api key",
Expand All @@ -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"),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -249,7 +269,7 @@ func TestUWebhooks(t *testing.T) {
t.Errorf("mismatch Log path. Got: %v, expected: %v", logPath, expectedLogPath)
}
}
*/
}
}
})
}
Expand Down
14 changes: 3 additions & 11 deletions pkg/k8s/admission-webhook/validating-webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/notifications/notifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func TestNewNotifiers(t *testing.T) {
},
{
name: "no config file specified",
wantErr: nil,
wantErr: ErrNotificationNotPresent,
},
}

Expand Down

0 comments on commit a3f26c1

Please sign in to comment.