Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds e2e tests for api server #585

Merged
merged 3 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Initializes Terrascan and clones policies from the Terrascan GitHub repository.
SilenceErrors: true,
}

func initial(cmd *cobra.Command, args []string, isScanCmd bool) error {
func initial(cmd *cobra.Command, args []string, nonInitCmd bool) error {
// initialize terrascan
if err := initialize.Run(isScanCmd); err != nil {
if err := initialize.Run(nonInitCmd); err != nil {
zap.S().Errorf("failed to initialize terrascan. error : %v", err)
return err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var serverCmd = &cobra.Command{
Run Terrascan as an API server that inspects incoming IaC (Infrastructure-as-Code) files and returns the scan results.
`,
PreRun: func(cmd *cobra.Command, args []string) {
initial(cmd, args, false)
initial(cmd, args, true)
},
Run: server,
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/config/config-reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func NewTerrascanConfigReader(fileName string) (*TerrascanConfigReader, error) {
// return error if file doesn't exist
_, err := os.Stat(fileName)
if err != nil {
zap.S().Error("config file: %s, doesn't exist", fileName)
zap.S().Errorf("config file: %s, doesn't exist", fileName)
return configReader, ErrNotPresent
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/http-server/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ const (

// APIVersion - default api version for REST endpoints
APIVersion = "v1"

// TerrascanServerPort allows user to configure server at a port other than default
TerrascanServerPort = "TERRASCAN_SERVER_PORT"
)
20 changes: 19 additions & 1 deletion pkg/http-server/file-scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"strconv"
"strings"

"github.com/accurics/terrascan/pkg/config"
"github.com/accurics/terrascan/pkg/runtime"
"github.com/accurics/terrascan/pkg/utils"
"github.com/gorilla/mux"
Expand Down Expand Up @@ -139,7 +140,7 @@ func (g *APIHandler) scanFile(w http.ResponseWriter, r *http.Request) {
tempFile.Name(), "", "", []string{"./testdata/testpolicies"}, scanRules, skipRules, severity)
} else {
executor, err = runtime.NewExecutor(iacType, iacVersion, cloudType,
tempFile.Name(), "", "", []string{}, scanRules, skipRules, severity)
tempFile.Name(), "", "", getPolicyPathFromEnv(), scanRules, skipRules, severity)
}
if err != nil {
zap.S().Error(err)
Expand Down Expand Up @@ -178,3 +179,20 @@ func (g *APIHandler) scanFile(w http.ResponseWriter, r *http.Request) {
// return that we have successfully uploaded our file!
apiResponse(w, string(j), http.StatusOK)
}

// getPolicyPathFromEnv reads the TERRASCAN_CONFIG env variable (if present) and returns the policy path
func getPolicyPathFromEnv() []string {
policyPath := []string{}

// read policy path from TERRASCAN_CONFIG env variable
terrascanConfigFile := os.Getenv("TERRASCAN_CONFIG")
if terrascanConfigFile != "" {
terrascanConfigReader, err := config.NewTerrascanConfigReader(terrascanConfigFile)
if err != nil {
zap.S().Errorf("error while reading config file, %s; err %v", terrascanConfigFile, err)
} else {
policyPath = append(policyPath, terrascanConfigReader.GetPolicyConfig().RepoPath)
}
}
return policyPath
}
2 changes: 1 addition & 1 deletion pkg/http-server/remote-repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (g *APIHandler) scanRemoteRepo(w http.ResponseWriter, r *http.Request) {
results, err = s.ScanRemoteRepo(iacType, iacVersion, cloudType, []string{"./testdata/testpolicies"})

} else {
results, err = s.ScanRemoteRepo(iacType, iacVersion, cloudType, []string{})
results, err = s.ScanRemoteRepo(iacType, iacVersion, cloudType, getPolicyPathFromEnv())
}
if err != nil {
apiErrorResponse(w, err.Error(), http.StatusBadRequest)
Expand Down
13 changes: 9 additions & 4 deletions pkg/http-server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,17 @@ func Start() {
// get all routes
routes := server.Routes()

serverPort := os.Getenv(TerrascanServerPort)
if serverPort == "" {
serverPort = GatewayDefaultPort
}

// register routes and start the http server
server.start(routes)
server.start(routes, serverPort)
}

// start http server
func (g *APIServer) start(routes []*Route) {
func (g *APIServer) start(routes []*Route, port string) {

var (
err error
Expand All @@ -58,7 +63,7 @@ func (g *APIServer) start(routes []*Route) {

// start http server
server := &http.Server{
Addr: ":" + GatewayDefaultPort,
Addr: ":" + port,
Handler: router,
}

Expand All @@ -68,7 +73,7 @@ func (g *APIServer) start(routes []*Route) {
logger.Fatal(err)
}
}()
logger.Infof("http server listening at port %v", GatewayDefaultPort)
logger.Infof("http server listening at port %v", port)

// Wait for interrupt signal to gracefully shutdown the server
quit := make(chan os.Signal, 1)
Expand Down
6 changes: 6 additions & 0 deletions test/e2e/server/golden/server_typo_help.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Error: unknown command "servre" for "terrascan"

Did you mean this?
server

Run 'terrascan --help' for usage.
245 changes: 245 additions & 0 deletions test/e2e/server/server_file_scan_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package server_test

import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"

"github.com/accurics/terrascan/pkg/iac-providers/output"
"github.com/accurics/terrascan/pkg/policy"
serverUtils "github.com/accurics/terrascan/test/e2e/server"
"github.com/accurics/terrascan/test/helper"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gbytes"
"github.com/onsi/gomega/gexec"
)

var _ = Describe("Server File Scan", func() {

var session *gexec.Session
var outWriter, errWriter io.Writer = gbytes.NewBuffer(), gbytes.NewBuffer()
port := "9012"

Context("file scan tests", func() {

JustBeforeEach(func() {
os.Setenv(terrascanServerPort, port)
})
JustAfterEach(func() {
os.Setenv(terrascanServerPort, "")
})

// launches a server session on port 9012
It("should start a new server session", func() {
session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, serverUtils.ServerCommand)
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say(fmt.Sprintf("http server listening at port %s", port)))
})

Context("terraform file scan", func() {
requestURL := fmt.Sprintf("%s:%s/v1/terraform/v12/all/local/file/scan", host, port)
When("iac file violates aws_db_instance_violation", func() {
It("should return violations successfully", func() {
iacFilePath, err := filepath.Abs(filepath.Join("..", "test_data", "iac", "aws", "aws_db_instance_violation", "main.tf"))
Expect(err).NotTo(HaveOccurred())

goldenFilePath, err := filepath.Abs(filepath.Join("..", "scan", "golden", "terraform_scans", "aws", "aws_db_instance_violations", "aws_db_instance_json.txt"))
Expect(err).NotTo(HaveOccurred())

responseBytes := serverUtils.MakeFileScanRequest(iacFilePath, requestURL, nil, http.StatusOK)
serverUtils.CompareResponseAndGoldenOutput(goldenFilePath, responseBytes)
})
})
When("iac file violates aws_ami", func() {
It("should return violations successfully", func() {
iacFilePath, err := filepath.Abs(filepath.Join("..", "test_data", "iac", "aws", "aws_ami_violation", "main.tf"))
Expect(err).NotTo(HaveOccurred())

goldenFilePath, err := filepath.Abs(filepath.Join("..", "scan", "golden", "terraform_scans", "aws", "aws_ami_violations", "aws_ami_violation_json.txt"))
Expect(err).NotTo(HaveOccurred())

responseBytes := serverUtils.MakeFileScanRequest(iacFilePath, requestURL, nil, http.StatusOK)
serverUtils.CompareResponseAndGoldenOutput(goldenFilePath, responseBytes)
})
})

Context("iac provider or iac version is invalid", func() {
errMessage := "iac type or version not supported"
iacFilePath, err := filepath.Abs(filepath.Join("..", "test_data", "iac", "aws", "aws_db_instance_violation", "main.tf"))

When("iac provider is invalid", func() {
It("should get and error response", func() {
requestURL := fmt.Sprintf("%s:%s/v1/%s/v12/all/local/file/scan", host, port, "terra")
Expect(err).NotTo(HaveOccurred())

serverUtils.MakeFileScanRequest(iacFilePath, requestURL, nil, http.StatusBadRequest)
Eventually(session.Err).Should(gbytes.Say(errMessage))
})
})
When("iac version is invalid", func() {
It("should get and error response", func() {
requestURL := fmt.Sprintf("%s:%s/v1/terraform/%s/all/local/file/scan", host, port, "v2")
Expect(err).NotTo(HaveOccurred())

serverUtils.MakeFileScanRequest(iacFilePath, requestURL, nil, http.StatusBadRequest)
Eventually(session.Err).Should(gbytes.Say(errMessage))
})
})
})

Context("body attributes present in the request", func() {
awsAmiIacFilePath, _ := filepath.Abs(filepath.Join("..", "test_data", "iac", "aws", "aws_ami_violation", "main.tf"))

When("config_only attribute is set", func() {

It("should receive resource config in response", func() {
bodyAttrs := make(map[string]string)
bodyAttrs["config_only"] = "true"

responseBytes := serverUtils.MakeFileScanRequest(awsAmiIacFilePath, requestURL, bodyAttrs, http.StatusOK)

var responseResourceConfig output.AllResourceConfigs
err := json.Unmarshal(responseBytes, &responseResourceConfig)
Expect(err).NotTo(HaveOccurred())

Expect(responseResourceConfig).To(HaveLen(1))
// the iac file only contains aws_ami resource
Expect(responseResourceConfig).To(HaveKey("aws_ami"))
})
})

When("show_passed attribute is set", func() {
It("should receive passed rules along with violations", func() {
iacFilePath, err := filepath.Abs(filepath.Join("..", "test_data", "iac", "aws", "aws_db_instance_violation", "main.tf"))
Expect(err).NotTo(HaveOccurred())

goldenFilePath, err := filepath.Abs(filepath.Join("..", "scan", "golden", "terraform_scans", "aws", "aws_db_instance_violations", "aws_db_instance_json_show_passed.txt"))
Expect(err).NotTo(HaveOccurred())

bodyAttrs := make(map[string]string)
bodyAttrs["show_passed"] = "true"
responseBytes := serverUtils.MakeFileScanRequest(iacFilePath, requestURL, bodyAttrs, http.StatusOK)
serverUtils.CompareResponseAndGoldenOutput(goldenFilePath, responseBytes)
})
})

Context("unknown body attributes are present", func() {
Context("api server ignores unknown attributes", func() {
It("should receive violations and 200 OK resopnse", func() {
bodyAttrs := make(map[string]string)
bodyAttrs["unknown_attribute"] = "someValue"

serverUtils.MakeFileScanRequest(awsAmiIacFilePath, requestURL, bodyAttrs, http.StatusOK)
})
})
})

Context("body attributes have invalid value", func() {
When("config_only has invalid value", func() {
It("should receive an error", func() {
bodyAttrs := make(map[string]string)
bodyAttrs["config_only"] = "invalid"

serverUtils.MakeFileScanRequest(awsAmiIacFilePath, requestURL, bodyAttrs, http.StatusBadRequest)
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say(`error while reading 'config_only' value. error: 'strconv.ParseBool: parsing "invalid": invalid syntax'`))
})
})

When("show_passed has invalid value", func() {
It("should receive an error", func() {
bodyAttrs := make(map[string]string)
bodyAttrs["show_passed"] = "invalid"

serverUtils.MakeFileScanRequest(awsAmiIacFilePath, requestURL, bodyAttrs, http.StatusBadRequest)
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say(`error while reading 'show_passed' value. error: 'strconv.ParseBool: parsing "invalid": invalid syntax'`))
})
})
})
})
})

Context("k8s file scan", func() {
It("should return violations successfully", func() {
Skip("add test case when https://github.com/accurics/terrascan/issues/584 is fixed")
})
})

Context("rules filtering options for file scan", func() {
requestURL := fmt.Sprintf("%s:%s/v1/terraform/v12/all/local/file/scan", host, port)
iacFilePath, _ := filepath.Abs(filepath.Join("..", "test_data", "iac", "aws", "aws_db_instance_violation", "main.tf"))

When("scan_rules is used", func() {
It("should receive violations and 200 OK resopnse", func() {

bodyAttrs := make(map[string]string)
bodyAttrs["scan_rules"] = "AWS.RDS.DS.High.1041"
responseBytes := serverUtils.MakeFileScanRequest(iacFilePath, requestURL, bodyAttrs, http.StatusOK)

var responseEngineOutput policy.EngineOutput
err := json.Unmarshal(responseBytes, &responseEngineOutput)
Expect(err).NotTo(HaveOccurred())

Expect(responseEngineOutput.ViolationStore.Summary.TotalPolicies).To(BeIdenticalTo(1))
})
})

When("skip_rules is used", func() {

It("should receive violations and 200 OK response", func() {
bodyAttrs := make(map[string]string)
bodyAttrs["skip_rules"] = "AWS.RDS.DataSecurity.High.0577"
responseBytes := serverUtils.MakeFileScanRequest(iacFilePath, requestURL, bodyAttrs, http.StatusOK)

var responseEngineOutput policy.EngineOutput
err := json.Unmarshal(responseBytes, &responseEngineOutput)
Expect(err).NotTo(HaveOccurred())

// There are total 7 rules in the test policies directory, out of which 1 is skipped
Expect(responseEngineOutput.ViolationStore.Summary.TotalPolicies).To(BeIdenticalTo(6))
})
})

When("scan and skip rules is used", func() {
It("should receive violations and 200 OK response", func() {
bodyAttrs := make(map[string]string)
bodyAttrs["scan_rules"] = "AWS.RDS.DS.High.1041,AWS.AWS RDS.NS.High.0101,AWS.RDS.DataSecurity.High.0577"
bodyAttrs["skip_rules"] = "AWS.RDS.DataSecurity.High.0577"
responseBytes := serverUtils.MakeFileScanRequest(iacFilePath, requestURL, bodyAttrs, http.StatusOK)

var responseEngineOutput policy.EngineOutput
err := json.Unmarshal(responseBytes, &responseEngineOutput)
Expect(err).NotTo(HaveOccurred())

// Total rules to be validated would be (scan_rules count - skip_rules count)
Expect(responseEngineOutput.ViolationStore.Summary.TotalPolicies).To(BeIdenticalTo(2))
})
})

When("severity is used", func() {
awsAmiIacFilePath, _ := filepath.Abs(filepath.Join("..", "test_data", "iac", "aws", "aws_ami_violation", "main.tf"))

When("severity is invalid", func() {
It("should receive a 400 bad request", func() {
bodyAttrs := make(map[string]string)
bodyAttrs["severity"] = "1"

serverUtils.MakeFileScanRequest(awsAmiIacFilePath, requestURL, bodyAttrs, http.StatusBadRequest)
Eventually(session.Err, serverUtils.ServerCommandTimeout).Should(gbytes.Say("severity level not supported"))
})
})

When("severity is valid", func() {
It("should receive violations result with 200 OK response", func() {
bodyAttrs := make(map[string]string)
bodyAttrs["severity"] = "HIGH"

serverUtils.MakeFileScanRequest(awsAmiIacFilePath, requestURL, bodyAttrs, http.StatusOK)
})
})
})
})
})
})
Loading