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

changes for argocd integration #724

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 4 additions & 0 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ RUN addgroup --gid 101 terrascan && \
adduser -S --uid 101 --ingroup terrascan terrascan && \
apk add --no-cache git openssh

# create .ssh folder and change owner to terrascan
RUN mkdir -p /home/terrascan/.ssh && \
chown -R terrascan:terrascan /home/terrascan/.ssh

jlk marked this conversation as resolved.
Show resolved Hide resolved
# run as non root user
USER terrascan

Expand Down
39 changes: 30 additions & 9 deletions pkg/http-server/remote-repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import (
"path/filepath"
"strings"

"github.com/accurics/terrascan/pkg/config"
"github.com/accurics/terrascan/pkg/downloader"
admissionwebhook "github.com/accurics/terrascan/pkg/k8s/admission-webhook"
"github.com/accurics/terrascan/pkg/runtime"
"github.com/accurics/terrascan/pkg/utils"
"github.com/gorilla/mux"
Expand Down Expand Up @@ -69,11 +71,12 @@ func (g *APIHandler) scanRemoteRepo(w http.ResponseWriter, r *http.Request) {
// scan remote repo
s.d = downloader.NewDownloader()
var results interface{}
var isAdmissionDenied bool
if g.test {
results, err = s.ScanRemoteRepo(iacType, iacVersion, cloudType, []string{"./testdata/testpolicies"})
results, isAdmissionDenied, err = s.ScanRemoteRepo(iacType, iacVersion, cloudType, []string{"./testdata/testpolicies"})

} else {
results, err = s.ScanRemoteRepo(iacType, iacVersion, cloudType, getPolicyPathFromConfig())
results, isAdmissionDenied, err = s.ScanRemoteRepo(iacType, iacVersion, cloudType, getPolicyPathFromConfig())
}
if err != nil {
apiErrorResponse(w, err.Error(), http.StatusBadRequest)
Expand All @@ -90,17 +93,23 @@ func (g *APIHandler) scanRemoteRepo(w http.ResponseWriter, r *http.Request) {
}

// return with results
// if result contain violations denied by admission controller return 403 status code
if isAdmissionDenied {
apiResponse(w, string(j), http.StatusForbidden)
return
}
apiResponse(w, string(j), http.StatusOK)
}

// ScanRemoteRepo is the actual method where a remote repo is downloaded and
// scanned for violations
func (s *scanRemoteRepoReq) ScanRemoteRepo(iacType, iacVersion string, cloudType []string, policyPath []string) (interface{}, error) {
func (s *scanRemoteRepoReq) ScanRemoteRepo(iacType, iacVersion string, cloudType []string, policyPath []string) (interface{}, bool, error) {

// return params
var (
output interface{}
err error
output interface{}
err error
isAdmissionDenied bool
)

// temp destination directory to download remote repo
Expand All @@ -112,23 +121,23 @@ func (s *scanRemoteRepoReq) ScanRemoteRepo(iacType, iacVersion string, cloudType
if err != nil {
errMsg := fmt.Sprintf("failed to download remote repo. error: '%v'", err)
zap.S().Error(errMsg)
return output, err
return output, isAdmissionDenied, err
}

// create a new runtime executor for scanning the remote repo
executor, err := runtime.NewExecutor(iacType, iacVersion, cloudType,
"", iacDirPath, policyPath, s.ScanRules, s.SkipRules, s.Categories, s.Severity)
if err != nil {
zap.S().Error(err)
return output, err
return output, isAdmissionDenied, err
}

// evaluate policies IaC for violations
results, err := executor.Execute()
if err != nil {
errMsg := fmt.Sprintf("failed to scan uploaded file. error: '%v'", err)
zap.S().Error(errMsg)
return output, err
return output, isAdmissionDenied, err
}

if !s.ShowPassed {
Expand All @@ -139,9 +148,21 @@ func (s *scanRemoteRepoReq) ScanRemoteRepo(iacType, iacVersion string, cloudType
if s.ConfigOnly {
output = results.ResourceConfig
} else {
isAdmissionDenied = hasK8sAdmissionDeniedViolations(results)
output = results.Violations
}

// succesful
return output, nil
return output, isAdmissionDenied, nil
}

// hasK8sAdmissionDeniedViolations checks if violations have denied by k8s admission controller
func hasK8sAdmissionDeniedViolations(o runtime.Output) bool {
denyRuleMatcher := admissionwebhook.WebhookDenyRuleMatcher{}
for _, v := range o.Violations.ViolationStore.Violations {
if denyRuleMatcher.Match(*v, config.GetK8sAdmissionControl()) {
return true
}
}
return false
}
62 changes: 61 additions & 1 deletion pkg/http-server/remote-repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
"reflect"
"testing"

"github.com/accurics/terrascan/pkg/config"
"github.com/accurics/terrascan/pkg/downloader"
"github.com/accurics/terrascan/pkg/policy"
"github.com/accurics/terrascan/pkg/results"
"github.com/accurics/terrascan/pkg/runtime"
"github.com/gorilla/mux"
)

Expand Down Expand Up @@ -69,7 +74,7 @@ func TestScanRemoteRepo(t *testing.T) {

for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
gotOutput, gotErr := tt.s.ScanRemoteRepo(tt.iacType, tt.iacVersion, tt.cloudType, []string{})
gotOutput, _, gotErr := tt.s.ScanRemoteRepo(tt.iacType, tt.iacVersion, tt.cloudType, []string{})
if !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("error got: '%v', want: '%v'", gotErr, tt.wantErr)
}
Expand Down Expand Up @@ -203,3 +208,58 @@ func TestScanRemoteRepoHandler(t *testing.T) {
})
}
}

func TestHasK8sAdmissionDeniedViolations(t *testing.T) {
k8sTestData := "k8s_testdata"
configFileWithCategoryDenied := filepath.Join(k8sTestData, "config-deny-category.toml")

type args struct {
o runtime.Output
}
tests := []struct {
name string
args args
want bool
conigFile string
}{
{
name: "result with no violations",
args: args{
o: runtime.Output{
Violations: policy.EngineOutput{
ViolationStore: &results.ViolationStore{},
},
},
},
want: false,
},
{
name: "result contains denied violations",
args: args{
o: runtime.Output{
Violations: policy.EngineOutput{
ViolationStore: &results.ViolationStore{
Violations: []*results.Violation{
{
Category: "Identity and Access Management",
},
},
},
},
},
},
want: true,
conigFile: configFileWithCategoryDenied,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := config.LoadGlobalConfig(tt.conigFile); err != nil {
t.Errorf("error while loading the config file '%s'", tt.conigFile)
}
if got := hasK8sAdmissionDeniedViolations(tt.args.o); got != tt.want {
t.Errorf("hasK8sAdmissionDeniedViolations() = %v, want %v", got, tt.want)
}
})
}
}
47 changes: 8 additions & 39 deletions pkg/iac-providers/kubernetes/v1/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,47 +159,16 @@ func readSkipRulesFromAnnotations(annotations map[string]interface{}, resourceID
return nil
}

skipRules := make([]output.SkipRule, 0)
if rules, ok := skipRulesFromAnnotations.([]interface{}); ok {
for _, rule := range rules {
if value, ok := rule.(map[string]interface{}); ok {
skipRule := getSkipRuleObject(value)
if skipRule != nil {
skipRules = append(skipRules, *skipRule)
}
} else {
zap.S().Debugf("each rule in %s must be of map type", terrascanSkip)
}
}
} else {
zap.S().Debugf("%s must be an array of {rule: ruleID, comment: reason for skipping}", terrascanSkip)
}

return skipRules
}

func getSkipRuleObject(m map[string]interface{}) *output.SkipRule {
var skipRule output.SkipRule
var rule, comment interface{}
var ok bool

// get rule, if rule not found return nil
if rule, ok = m[terrascanSkipRule]; ok {
if _, ok = rule.(string); ok {
skipRule.Rule = rule.(string)
} else {
if rules, ok := skipRulesFromAnnotations.(string); ok {
skipRules := make([]output.SkipRule, 0)
err := json.Unmarshal([]byte(rules), &skipRules)
if err != nil {
zap.S().Debugf("terrascanSkip rules '%s' cannot be unmarshalled to json", rules)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
zap.S().Debugf("terrascanSkip rules '%s' cannot be unmarshalled to json", rules)
zap.S().Debugf("json string %s cannot be unmarshalled to [ ]output.SkipRules struct schema", rules)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devang's comment is technically accurate, but not sure if that much detail would be useful to average user, or confusing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @jlk. This change will not be informative to most users.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug is also used by us developers.. to diagnose what went wrong. Will ease it for us to help people reporting bugs and other issues.

return nil
}
} else {
return nil
}

// get comment
if comment, ok = m[terrascanSkipComment]; ok {
if _, ok = comment.(string); ok {
skipRule.Comment = comment.(string)
}
return skipRules
}

return &skipRule
zap.S().Debugf("%s must be a string containing an json array like [{rule: ruleID, comment: reason for skipping}]", terrascanSkip)
return nil
}
Loading