diff --git a/cmd/recommend.go b/cmd/recommend.go index 5f60be4f..66c66c9e 100644 --- a/cmd/recommend.go +++ b/cmd/recommend.go @@ -4,7 +4,10 @@ package cmd import ( + "fmt" + "github.com/kubearmor/kubearmor-client/recommend" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -22,9 +25,26 @@ var recommendCmd = &cobra.Command{ return nil }, } +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Updates policy-template cache", + Long: "Updates the local cache of policy-templates ($HOME/.cache/karmor)", + RunE: func(cmd *cobra.Command, args []string) error { + + if d, err := recommend.DownloadAndUnzipRelease(); err != nil { + fmt.Println(d) + return err + } + log.WithFields(log.Fields{ + "Current Version": recommend.CurrentVersion, + }).Info("policy-templates updated") + return nil + }, +} func init() { rootCmd.AddCommand(recommendCmd) + recommendCmd.AddCommand(updateCmd) recommendCmd.Flags().StringSliceVarP(&recommendOptions.Images, "image", "i", []string{}, "Container image list (comma separated)") recommendCmd.Flags().StringSliceVarP(&recommendOptions.Labels, "labels", "l", []string{}, "User defined labels for policy (comma separated)") diff --git a/go.mod b/go.mod index 2cfe6c25..183459cd 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,11 @@ require ( sigs.k8s.io/yaml v1.3.0 ) -require k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 +require ( + github.com/cavaliergopher/grab/v3 v3.0.1 + github.com/google/go-github v17.0.0+incompatible + k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 +) require ( cloud.google.com/go/compute v1.7.0 // indirect diff --git a/go.sum b/go.sum index 15ac7350..5c631127 100644 --- a/go.sum +++ b/go.sum @@ -167,6 +167,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= +github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= @@ -528,6 +530,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= diff --git a/recommend/imageHandler.go b/recommend/imageHandler.go index fc731642..334d48c7 100644 --- a/recommend/imageHandler.go +++ b/recommend/imageHandler.go @@ -217,6 +217,10 @@ func saveImageToTar(imageName string) string { func checkForSpec(spec string, fl []string) []string { var matches []string + if !strings.HasSuffix(spec, "*") { + spec = fmt.Sprintf("%s$", spec) + } + re := regexp.MustCompile(spec) for _, name := range fl { if re.Match([]byte(name)) { diff --git a/recommend/policy.go b/recommend/policy.go index 9751516e..655d671b 100644 --- a/recommend/policy.go +++ b/recommend/policy.go @@ -9,93 +9,24 @@ import ( "path/filepath" "strings" - "github.com/accuknox/auto-policy-discovery/src/types" "github.com/clarketm/json" "github.com/fatih/color" + pol "github.com/kubearmor/KubeArmor/pkg/KubeArmorPolicy/api/security.kubearmor.com/v1" log "github.com/sirupsen/logrus" "k8s.io/utils/strings/slices" "sigs.k8s.io/yaml" ) -func (r *SysRule) convertToKnoxRule() types.KnoxSys { - knoxRule := types.KnoxSys{} - fromSourceArr := []types.KnoxFromSource{} - - for _, eachSource := range r.FromSource { - if eachSource != "" { - if strings.HasSuffix(eachSource, "/") { - fromSourceArr = append(fromSourceArr, types.KnoxFromSource{ - Dir: eachSource, - }) - } else { - fromSourceArr = append(fromSourceArr, types.KnoxFromSource{ - Path: eachSource, - }) - } - } - } - - for _, path := range r.Path { - if strings.HasSuffix(path, "/") { - - dirRule := types.KnoxMatchDirectories{ - Dir: path, - Recursive: r.Recursive, - FromSource: fromSourceArr, - OwnerOnly: r.OwnerOnly, - } - knoxRule.MatchDirectories = append(knoxRule.MatchDirectories, dirRule) - } else { - pathRule := types.KnoxMatchPaths{ - Path: path, - FromSource: fromSourceArr, - OwnerOnly: r.OwnerOnly, - } - knoxRule.MatchPaths = append(knoxRule.MatchPaths, pathRule) - } - } - - return knoxRule -} - -// networkToKnoxRule function to include KubeArmor network rules -func (r *NetRule) networkToKnoxRule() types.NetworkRule { - knoxNetRule := types.NetworkRule{} - fromSourceArr := []types.KnoxFromSource{} - - if r.FromSource != "" { - if strings.HasSuffix(r.FromSource, "/") { - fromSourceArr = append(fromSourceArr, types.KnoxFromSource{ - Dir: r.FromSource, - }) - } else { - fromSourceArr = append(fromSourceArr, types.KnoxFromSource{ - Path: r.FromSource, - }) - } - } - for _, protocol := range r.Protocol { - - protoRule := types.KnoxMatchProtocols{ - Protocol: protocol, - FromSource: fromSourceArr, - } - knoxNetRule.MatchProtocols = append(knoxNetRule.MatchProtocols, protoRule) +func addPolicyRule(policy *pol.KubeArmorPolicy, r pol.KubeArmorPolicySpec) { + if len(r.File.MatchDirectories) != 0 || len(r.File.MatchPaths) != 0 { + policy.Spec.File = r.File } - - return knoxNetRule -} - -func addPolicyRule(policy *types.KubeArmorPolicy, r Rules) { - if r.FileRule != nil { - policy.Spec.File = r.FileRule.convertToKnoxRule() - } - if r.ProcessRule != nil { - policy.Spec.Process = r.ProcessRule.convertToKnoxRule() + if len(r.Process.MatchDirectories) != 0 || len(r.Process.MatchPaths) != 0 { + policy.Spec.Process = r.Process } - if r.NetworkRule != nil { - policy.Spec.Network = r.NetworkRule.networkToKnoxRule() + if len(r.Network.MatchProtocols) != 0 { + policy.Spec.Network = r.Network } } @@ -110,31 +41,30 @@ func mkPathFromTag(tag string) string { return r.Replace(tag) } -func (img *ImageInfo) createPolicy(ms MatchSpec) (types.KubeArmorPolicy, error) { - policy := types.KubeArmorPolicy{ - APIVersion: "security.kubearmor.com/v1", - Kind: "KubeArmorPolicy", - Metadata: map[string]string{}, - Spec: types.KnoxSystemSpec{ +func (img *ImageInfo) createPolicy(ms MatchSpec) (pol.KubeArmorPolicy, error) { + policy := pol.KubeArmorPolicy{ + Spec: pol.KubeArmorPolicySpec{ Severity: 1, // by default - Selector: types.Selector{ + Selector: pol.SelectorType{ MatchLabels: map[string]string{}}, }, } + policy.APIVersion = "security.kubearmor.com/v1" + policy.Kind = "KubeArmorPolicy" - policy.Metadata["name"] = img.getPolicyName(ms.Name) + policy.ObjectMeta.Name = img.getPolicyName(ms.Name) if img.Namespace != "" { - policy.Metadata["namespace"] = img.Namespace + policy.ObjectMeta.Namespace = img.Namespace } - policy.Spec.Action = ms.OnEvent.Action - policy.Spec.Severity = ms.OnEvent.Severity - if ms.OnEvent.Message != "" { - policy.Spec.Message = ms.OnEvent.Message + policy.Spec.Action = ms.Spec.Action + policy.Spec.Severity = ms.Spec.Severity + if ms.Spec.Message != "" { + policy.Spec.Message = ms.Spec.Message } - if len(ms.OnEvent.Tags) > 0 { - policy.Spec.Tags = ms.OnEvent.Tags + if len(ms.Spec.Tags) > 0 { + policy.Spec.Tags = ms.Spec.Tags } if len(img.Labels) > 0 { @@ -144,13 +74,16 @@ func (img *ImageInfo) createPolicy(ms MatchSpec) (types.KubeArmorPolicy, error) policy.Spec.Selector.MatchLabels["kubearmor.io/container.name"] = repotag[0] } - addPolicyRule(&policy, ms.Rules) + addPolicyRule(&policy, ms.Spec) return policy, nil } func (img *ImageInfo) checkPreconditions(ms MatchSpec) bool { - matches := checkForSpec(filepath.Join(ms.Precondition), img.FileList) - return len(matches) > 0 + var matches []string + for _, preCondition := range ms.Precondition { + matches = append(matches, checkForSpec(filepath.Join(preCondition), img.FileList)...) + } + return len(matches) >= len(ms.Precondition) } func matchTags(ms MatchSpec) bool { @@ -158,7 +91,7 @@ func matchTags(ms MatchSpec) bool { return true } for _, t := range options.Tags { - if slices.Contains(ms.OnEvent.Tags, t) { + if slices.Contains(ms.Spec.Tags, t) { return true } } @@ -214,8 +147,7 @@ func (img *ImageInfo) getPolicyFromImageInfo() { err = createRuntimePolicy(img) if err != nil { - log.WithError(err).Error("Failed to create runtime policy") - return + log.Infof("Failed to create runtime policy for %s/%s/%s. err=%s", img.Namespace, img.Deployment, img.Name, err) } ms, err = getNextRule(&idx) diff --git a/recommend/policyRules.go b/recommend/policyRules.go index 647d9712..d7a6301d 100644 --- a/recommend/policyRules.go +++ b/recommend/policyRules.go @@ -12,16 +12,17 @@ import ( "sigs.k8s.io/yaml" "github.com/fatih/color" + pol "github.com/kubearmor/KubeArmor/pkg/KubeArmorPolicy/api/security.kubearmor.com/v1" log "github.com/sirupsen/logrus" ) // MatchSpec spec to match for defining policy type MatchSpec struct { - Name string `json:"name" yaml:"name"` - Precondition string `json:"precondition" yaml:"precondition"` - Description Description `json:"description" yaml:"description"` - Rules Rules `json:"rules" yaml:"rules"` - OnEvent OnEvent `json:"onEvent" yaml:"onEvent"` + Name string `json:"name" yaml:"name"` + Precondition []string `json:"precondition" yaml:"precondition"` + Description Description `json:"description" yaml:"description"` + Yaml string `json:"yaml" yaml:"yaml"` + Spec pol.KubeArmorPolicySpec `json:"spec,omitempty" yaml:"spec,omitempty"` } // Ref for the policy rules @@ -37,59 +38,33 @@ type Description struct { Detailed string `json:"detailed" yaml:"detailed"` } -// SysRule specifics a file/process rule. Note that if the Path ends in "/" it is considered to be Directory rule -type SysRule struct { - FromSource []string `json:"fromSource" yaml:"fromSource"` - Path []string `json:"path" yaml:"path"` - Recursive bool `json:"recursive" yaml:"recursive"` - OwnerOnly bool `json:"ownerOnly" yaml:"ownerOnly"` -} - -// NetRule specifies a KubeArmor network rule. -type NetRule struct { - FromSource string `json:"fromSource" yaml:"fromSource"` - Protocol []string `json:"protocol" yaml:"protocol"` -} - -// Rules set of applicable rules. In the future, we might have other types of rules. -type Rules struct { - FileRule *SysRule `json:"fileRule" yaml:"fileRule"` - ProcessRule *SysRule `json:"processRule" yaml:"processRule"` - NetworkRule *NetRule `json:"networkRule" yaml:"networkRule"` -} - -// OnEvent the information that is emitted in the telemetry/alert when the matching event is witnessed -type OnEvent struct { - Severity int `json:"severity" yaml:"severity"` - Message string `json:"message" yaml:"message"` - Tags []string `json:"tags" yaml:"tags"` - Action string `json:"action" yaml:"action"` -} - var policyRules []MatchSpec //go:embed yaml/rules.yaml var policyRulesYAML []byte -func init() { - policyRulesJSON, err := yaml.YAMLToJSON(policyRulesYAML) +func updateRulesYAML(yamlFile []byte) string { + policyRules = []MatchSpec{} + if len(yamlFile) < 30 { + yamlFile = policyRulesYAML + } + policyRulesJSON, err := yaml.YAMLToJSON(yamlFile) if err != nil { color.Red("failed to convert policy rules yaml to json") log.WithError(err).Fatal("failed to convert policy rules yaml to json") } - var jsonRaw map[string]json.RawMessage err = json.Unmarshal(policyRulesJSON, &jsonRaw) if err != nil { color.Red("failed to unmarshal policy rules json") log.WithError(err).Fatal("failed to unmarshal policy rules json") } - err = json.Unmarshal(jsonRaw["policyRules"], &policyRules) if err != nil { color.Red("failed to unmarshal policy rules") log.WithError(err).Fatal("failed to unmarshal policy rules") } + return string(jsonRaw["version"]) } func getNextRule(idx *int) (MatchSpec, error) { diff --git a/recommend/policyTemplates.go b/recommend/policyTemplates.go new file mode 100644 index 00000000..64e29570 --- /dev/null +++ b/recommend/policyTemplates.go @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Authors of KubeArmor + +package recommend + +import ( + "archive/zip" + "context" + "fmt" + "os" + "path" + "path/filepath" + "runtime" + "strings" + + "github.com/cavaliergopher/grab/v3" + "github.com/google/go-github/github" + pol "github.com/kubearmor/KubeArmor/pkg/KubeArmorPolicy/api/security.kubearmor.com/v1" + log "github.com/sirupsen/logrus" + "sigs.k8s.io/yaml" +) + +const ( + org = "kubearmor" + repo = "policy-templates" + url = "https://github.com/kubearmor/policy-templates/archive/refs/tags/" + cache = ".cache/karmor/" +) + +// CurrentVersion stores the current version of policy-template +var CurrentVersion string + +func getCachePath() string { + cache := fmt.Sprintf("%s/%s", userHome(), cache) + return cache + +} + +// userHome function returns users home directory +func userHome() string { + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + } + return os.Getenv("HOME") +} + +func latestRelease() string { + latestRelease, _, err := github.NewClient(nil).Repositories.GetLatestRelease(context.Background(), org, repo) + if err != nil { + log.WithError(err) + return "" + } + return *latestRelease.TagName +} + +// CurrentRelease gets the current release of policy-templates +func CurrentRelease() string { + + path, err := os.ReadFile(fmt.Sprintf("%s%s", getCachePath(), "rules.yaml")) + if err != nil { + CurrentVersion = strings.Trim(updateRulesYAML([]byte{}), "\"") + } else { + + CurrentVersion = strings.Trim(updateRulesYAML(path), "\"") + } + + return CurrentVersion +} + +func isLatest() bool { + latest := latestRelease() + + if latest == "" { + // error while fetching latest release tag + // assume the current release is the latest one + return true + } + return (CurrentVersion == latest) +} + +func removeData(file string) error { + err := os.RemoveAll(file) + return err +} + +func init() { + CurrentVersion = CurrentRelease() +} + +// DownloadAndUnzipRelease downloads the latest version of policy-templates +func DownloadAndUnzipRelease() (string, error) { + + latestRelease := latestRelease() + + _ = removeData(getCachePath()) + err := os.MkdirAll(filepath.Dir(getCachePath()), 0750) + if err != nil { + return "", err + } + downloadURL := fmt.Sprintf("%s%s.zip", url, latestRelease) + resp, err := grab.Get(getCachePath(), downloadURL) + if err != nil { + _ = removeData(getCachePath()) + return "", err + } + err = unZip(resp.Filename, getCachePath()) + if err != nil { + return "", err + } + err = removeData(resp.Filename) + if err != nil { + return "", err + } + _ = updatePolicyRules(strings.TrimSuffix(resp.Filename, ".zip")) + CurrentVersion = CurrentRelease() + return latestRelease, nil +} + +func unZip(source, dest string) error { + read, err := zip.OpenReader(source) + if err != nil { + return err + } + defer read.Close() + for _, file := range read.File { + if file.Mode().IsDir() { + continue + } + open, err := file.Open() + if err != nil { + return err + } + name, err := sanitizeArchivePath(dest, file.Name) + if err != nil { + return err + } + _ = os.MkdirAll(path.Dir(name), 0750) + create, err := os.Create(filepath.Clean(name)) + if err != nil { + return err + } + _, err = create.ReadFrom(open) + if err != nil { + return err + } + if err = create.Close(); err != nil { + return err + } + defer open.Close() + } + return nil +} + +func updatePolicyRules(filePath string) error { + var files []string + err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && info.Name() == "metadata.yaml" { + files = append(files, path) + } + return nil + }) + if err != nil { + return err + } + rulesYamlPath := filepath.Join(getCachePath(), "rules.yaml") + f, err := os.Create(filepath.Clean(rulesYamlPath)) + if err != nil { + log.WithError(err).Errorf("Failed to create %s", rulesYamlPath) + } + + var yamlFile []byte + var completePolicy []MatchSpec + var version string + + for _, file := range files { + + idx := 0 + yamlFile, err = os.ReadFile(filepath.Clean(file)) + if err != nil { + return err + } + version = updateRulesYAML(yamlFile) + ms, err := getNextRule(&idx) + for ; err == nil; ms, err = getNextRule(&idx) { + if ms.Yaml != "" { + newPolicyFile := pol.KubeArmorPolicy{} + newYaml, err := os.ReadFile(filepath.Clean(fmt.Sprintf("%s%s", strings.TrimSuffix(file, "metadata.yaml"), ms.Yaml))) + if err != nil { + return err + } + err = yaml.Unmarshal(newYaml, &newPolicyFile) + if err != nil { + return err + } + ms.Yaml = "" + ms.Spec = newPolicyFile.Spec + } + completePolicy = append(completePolicy, ms) + } + } + + yamlFile, err = yaml.Marshal(completePolicy) + if err != nil { + return err + } + version = strings.Trim(version, "\"") + yamlFile = []byte(fmt.Sprintf("version: %s\npolicyRules:\n%s", version, yamlFile)) + if _, err := f.WriteString(string(yamlFile)); err != nil { + log.WithError(err).Error("WriteString failed") + } + if err := f.Sync(); err != nil { + log.WithError(err).Error("file sync failed") + } + if err := f.Close(); err != nil { + log.WithError(err).Error("file close failed") + } + return nil +} diff --git a/recommend/recommend.go b/recommend/recommend.go index 6b246273..a58fd1a7 100644 --- a/recommend/recommend.go +++ b/recommend/recommend.go @@ -89,6 +89,12 @@ func Recommend(c *k8s.Client, o Options) error { deployments := []Deployment{} var err error + yamlFile, _ := os.ReadFile(filepath.Join(getCachePath(), "rules.yaml")) + CurrentVersion = updateRulesYAML(yamlFile) + if !isLatest() { + log.Warn("\033[1;33mA new version of policy-templates is available. Use `karmor recommend update` to get recommendations based on the latest policy-templates.\033[0m") + } + if err = createOutDir(o.OutDir); err != nil { return err } diff --git a/recommend/report_html.go b/recommend/report_html.go index 9f20c5d0..bce27909 100644 --- a/recommend/report_html.go +++ b/recommend/report_html.go @@ -10,7 +10,6 @@ import ( "html/template" "os" "path/filepath" - "strconv" "strings" "time" @@ -146,9 +145,9 @@ func (r HTMLReport) Record(ms MatchSpec, policyName string) error { Rec: []Col{ {Name: policyName}, {Name: ms.Description.Tldr}, - {Name: strconv.Itoa(ms.OnEvent.Severity)}, - {Name: ms.OnEvent.Action}, - {Name: strings.Join(ms.OnEvent.Tags[:], ",")}, + {Name: fmt.Sprintf("%d", ms.Spec.Severity)}, + {Name: string(ms.Spec.Action)}, + {Name: strings.Join(ms.Spec.Tags[:], ",")}, }, Policy: string(policy), Description: ms.Description.Detailed, diff --git a/recommend/report_text.go b/recommend/report_text.go index 8551e9a5..33dfccfd 100644 --- a/recommend/report_text.go +++ b/recommend/report_text.go @@ -7,7 +7,6 @@ import ( _ "embed" // need for embedding "fmt" "os" - "strconv" "strings" "github.com/olekukonko/tablewriter" @@ -67,9 +66,9 @@ func (r TextReport) Record(ms MatchSpec, policyName string) error { rec = append(rec, wrapPolicyName(policyName, 35)) rec = append(rec, ms.Description.Tldr) - rec = append(rec, strconv.Itoa(ms.OnEvent.Severity)) - rec = append(rec, ms.OnEvent.Action) - rec = append(rec, strings.Join(ms.OnEvent.Tags[:], ",")) + rec = append(rec, fmt.Sprintf("%d", ms.Spec.Severity)) + rec = append(rec, string(ms.Spec.Action)) + rec = append(rec, strings.Join(ms.Spec.Tags[:], ",")) r.table.Append(rec) return nil } diff --git a/recommend/runtimePolicy.go b/recommend/runtimePolicy.go index 6f85fd01..6c417f7b 100644 --- a/recommend/runtimePolicy.go +++ b/recommend/runtimePolicy.go @@ -11,6 +11,8 @@ import ( "strings" opb "github.com/accuknox/auto-policy-discovery/src/protobuf/v1/observability" + pol "github.com/kubearmor/KubeArmor/pkg/KubeArmorPolicy/api/security.kubearmor.com/v1" + log "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -66,35 +68,50 @@ func createRuntimePolicy(img *ImageInfo) error { sumResp = append(sumResp, resp) } - ms, err := checkProcessFileData(sumResp) - if err != nil { - return err + ms := checkProcessFileData(sumResp, img.Distro) + if ms != nil { + img.writePolicyFile(*ms) } - img.writePolicyFile(ms) return nil } -func checkProcessFileData(sumResp []*opb.Response) (MatchSpec, error) { - var filePaths SysRule - ms := MatchSpec{} - ms.OnEvent.Severity = 1 - ms.Description.Tldr = "Kubernetes serviceaccount folder access should be limited " - ms.Name = "audit-serviceaccount-runtime" - ms.OnEvent.Tags = []string{"KUBERNETES", "SERVICE ACCOUNT", "RUNTIME POLICY"} - ms.OnEvent.Message = "serviceaccount access detected" - ms.OnEvent.Action = "Audit" +func checkProcessFileData(sumResp []*opb.Response, distro string) *MatchSpec { + var filePaths pol.FileType + fromSourceArr := []pol.MatchSourceType{} + ms := MatchSpec{ + Name: "audit-serviceaccount-runtime", + Description: Description{ + Tldr: "Kubernetes serviceaccount folder access should be limited", + }, + } for _, eachResp := range sumResp { for _, fileData := range eachResp.FileData { if strings.HasPrefix(fileData.ProcName, saPath[0]) || strings.HasPrefix(fileData.ProcName, saPath[1]) { - filePaths.FromSource = append(filePaths.FromSource, fileData.ParentProcName) + fromSourceArr = append(fromSourceArr, pol.MatchSourceType{ + Path: pol.MatchPathType(fileData.ParentProcName), + }) } } } - filePaths.Path = saPath - filePaths.Recursive = true + if len(fromSourceArr) < 1 { + log.Info("No serviceaccount access detected. Skipping runtime policy creation on ", distro) + return nil - ms.Rules = Rules{ - FileRule: &filePaths, } - return ms, nil + filePaths.MatchDirectories = append(filePaths.MatchDirectories, pol.FileDirectoryType{ + Directory: pol.MatchDirectoryType(saPath[0]), + FromSource: fromSourceArr, + }) + filePaths.MatchDirectories = append(filePaths.MatchDirectories, pol.FileDirectoryType{ + Directory: pol.MatchDirectoryType(saPath[1]), + FromSource: fromSourceArr, + }) + ms.Spec = pol.KubeArmorPolicySpec{ + Action: "Allow", + Message: "serviceaccount access detected", + Tags: []string{"KUBERNETES", "SERVICE ACCOUNT", "RUNTIME POLICY"}, + Severity: 1, + File: filePaths, + } + return &ms } diff --git a/recommend/yaml/rules.yaml b/recommend/yaml/rules.yaml index b2533f9a..66bfbb44 100644 --- a/recommend/yaml/rules.yaml +++ b/recommend/yaml/rules.yaml @@ -1,6 +1,8 @@ +version: v0.0.1 policyRules: - name: cert-access - precondition: "/etc/ssl/.*" + precondition: + - /etc/ssl/.* description: refs: - name: MITRE-TTP @@ -18,22 +20,22 @@ policyRules: signing certificate, a program prompting the user with a warning because it has an attribute set from being downloaded from the Internet, or getting an indication that you are about to connect to an untrusted site. - rules: - fileRule: - path: - - "/etc/ssl/" - - "/etc/pki/" - - "/usr/local/share/ca-certificates/" - recursive: true - onEvent: + spec: severity: 2 message: restrict access to certificate data tags: - PCI-DSS - MITRE action: Audit + file: + matchDirectories: + - dir: "/etc/ssl/" + - dir: "/etc/pki/" + - dir: "/usr/local/share/ca-certificates/" + recursive: true - name: sys-bin-protect - precondition: "/bin/.*" + precondition: + - /bin/.* description: refs: - name: MITRE-TTP @@ -42,22 +44,22 @@ policyRules: tldr: create or modify system-level processes for persistence of malicious payloads detailed: Adversaries may create or modify system-level processes to repeatedly execute malicious payloads as part of persistence. - rules: - fileRule: - path: - - "/bin/" - - "/usr/bin/" - - "/usr/sbin/" - recursive: true - onEvent: + spec: severity: 1 message: attempted access to system binaries tags: - CIS - MITRE action: Audit + file: + matchDirectories: + - dir: "/bin/" + - dir: "/usr/bin/" + - dir: "/usr/sbin/" + recursive: true - name: password-protect - precondition: "/etc/passwd" + precondition: + - /etc/passwd description: refs: - name: MITRE-TTP @@ -67,20 +69,20 @@ policyRules: detailed: Adversaries may search for common password storage locations to obtain user credentials. Passwords are stored in several places on a system, depending on the operating system or application holding the credentials. - rules: - fileRule: - path: - - "/etc/passwd" - - "/etc/shadow" - onEvent: + spec: severity: 1 message: attempted access to password files tags: - CIS - MITRE action: Audit + file: + matchPaths: + - path: "/etc/passwd" + - path: "/etc/shadow" - name: scheduler-protect - precondition: "/etc/crontab" + precondition: + - /etc/crontab description: refs: - name: MITRE-TTP @@ -91,20 +93,20 @@ policyRules: detailed: |- Adversaries may abuse task scheduling functionality to facilitate initial or recurring execution of malicious code. Utilities exist within all major operating systems to schedule programs or scripts to be executed at a specified date and time. Adversaries may use task scheduling to execute programs at system startup or on a scheduled basis for persistence. These mechanisms can also be abused to run a process under the context of a specified account (such as one with elevated permissions/privileges). - rules: - fileRule: - path: - - "/etc/crontab" - - "/etc/at.allow" - - "/etc/at.deny" - onEvent: + spec: severity: 1 message: attempted access to cronjob settings tags: - MITRE action: Audit + file: + matchPaths: + - path: "/etc/crontab" + - path: "/etc/at.allow" + - path: "/etc/at.deny" - name: maint-tool-access - precondition: "/sbin/apk" + precondition: + - /sbin/apk description: refs: - name: MITRE-TTP @@ -114,20 +116,20 @@ policyRules: detailed: Container images might contain maintenance tools which should ideally never be used in prod env, or if used, should be used only in certain time frames. Examples include, dynamic package management tools, mii-tool, iptables etc - rules: - fileRule: - path: - - "/sbin/" - recursive: true - onEvent: + spec: severity: 1 message: restricted maintenance tool access attempted tags: - PCI-DSS - MITRE action: Audit + file: + matchDirectories: + - dir: "/sbin/" + recursive: true - name: shell-access - precondition: "/bin/sh" + precondition: + - /bin/sh description: refs: - name: MITRE-TTP @@ -136,24 +138,24 @@ policyRules: tldr: abuse shell access and execute arbitrary commands, scripts, or binaries detailed: Containers comes with some built-in shell and scripting capabilities. Adversaries may attempt to use the shell to execute arbitrary commands. - rules: - processRule: - path: - - "/bin/sh" - - "/bin/bash" - - "/bin/dash" - - "/bin/ksh" - - "/bin/zsh" - - "/bin/tcsh" - - "/bin/csh" - onEvent: + spec: severity: 1 message: attempted access to shell tags: - MITRE action: Audit + process: + matchPaths: + - path: "/bin/sh" + - path: "/bin/bash" + - path: "/bin/dash" + - path: "/bin/ksh" + - path: "/bin/zsh" + - path: "/bin/tcsh" + - path: "/bin/csh" - name: ssh-access - precondition: "/usr/bin/ssh" + precondition: + - /usr/bin/ssh description: refs: - name: MITRE-TTP @@ -161,18 +163,18 @@ policyRules: - https://attack.mitre.org/techniques/T1021/ tldr: use ssh to gain access to host in the same network detailed: To accomplish Lateral Movement, adversaries can use SSH to login into host in the same network. - rules: - processRule: - path: - - "/usr/bin/ssh" - onEvent: + spec: severity: 1 message: attempted access to SSH tags: - MITRE action: Audit + process: + matchPaths: + - path: "/usr/bin/ssh" - name: nist-ca-3-net-icmp-audit - precondition: "/bin/busybox" + precondition: + - /bin/busybox description: refs: - name: NIST-CA-3 @@ -188,11 +190,7 @@ policyRules: requirements, controls, and responsibilities for each system, and the impact level of the information communicated; and Review and update the agreements [Assignment organization-defined frequency]. - rules: - networkRule: - protocol: - - "icmp" - onEvent: + spec: severity: 1 message: Detected Network traffic using ICMP packets tags: @@ -200,4 +198,8 @@ policyRules: - NIST-800-CA-3 - NETWORK - System-Interconnections - action: Audit \ No newline at end of file + action: Audit + network: + matchProtocol: + - protocol: "icmp" + \ No newline at end of file diff --git a/selfupdate/selfupdate.go b/selfupdate/selfupdate.go index 2997d4f9..6c95671f 100644 --- a/selfupdate/selfupdate.go +++ b/selfupdate/selfupdate.go @@ -27,13 +27,11 @@ const ghrepo = "kubearmor/kubearmor-client" func isValidVersion(ver string) bool { _, err := semver.Make(ver) - if err != nil { - return false - } - return true + return err == nil } -func confirmUserAction(action string) bool { +// ConfirmUserAction - returns true if user inputs `y` +func ConfirmUserAction(action string) bool { fmt.Printf("%s (y/n): ", action) input, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil || (input != "y\n" && input != "n\n") { @@ -59,7 +57,7 @@ func getLatest() (*selfupdate.Release, error) { return latest, nil } -//IsLatest - check if the current binary is the latest +// IsLatest - check if the current binary is the latest func IsLatest(curver string) (bool, string) { if curver != "" && !isValidVersion(curver) { return true, "" @@ -114,7 +112,7 @@ func SelfUpdate(c *k8s.Client) error { fmt.Printf("current karmor version %s\n", ver) if !isValidVersion(ver) { fmt.Println("version does not match the pattern. Maybe using a locally built karmor!") - if !confirmUserAction("Do you want to update it?") { + if !ConfirmUserAction("Do you want to update it?") { return nil } return doSelfUpdate("")