From e363753ad80ecab77548ecaf825e0a67e1870465 Mon Sep 17 00:00:00 2001 From: Prateek Nandle Date: Thu, 4 Jan 2024 16:49:47 +0530 Subject: [PATCH] fixed audit event alerts for allowed network rules Signed-off-by: Prateek Nandle --- KubeArmor/feeder/policyMatcher.go | 172 ++++++++++-------- tests/k8s_env/block/block_test.go | 5 +- .../smoke/res/ksp-wordpress-allow-tcp.yaml | 18 ++ tests/k8s_env/smoke/smoke_test.go | 42 +++++ 4 files changed, 163 insertions(+), 74 deletions(-) create mode 100644 tests/k8s_env/smoke/res/ksp-wordpress-allow-tcp.yaml diff --git a/KubeArmor/feeder/policyMatcher.go b/KubeArmor/feeder/policyMatcher.go index fe952f4bad..27c8962473 100644 --- a/KubeArmor/feeder/policyMatcher.go +++ b/KubeArmor/feeder/policyMatcher.go @@ -35,6 +35,20 @@ func GetProtocolFromName(proto string) string { } } +func fetchProtocol(resource string) string { + if strings.Contains(resource, "protocol=TCP") || (strings.Contains(resource, "SOCK_STREAM") && strings.Contains(resource, "protocol=0")) { + return "tcp" + } else if strings.Contains(resource, "protocol=UDP") || (strings.Contains(resource, "SOCK_DGRAM") && strings.Contains(resource, "protocol=0")) { + return "udp" + } else if strings.Contains(resource, "protocol=ICMP") { + return "icmp" + } else if strings.Contains(resource, "SOCK_RAW") { + return "raw" + } + + return "unknown" +} + func getFileProcessUID(path string) string { info, err := os.Stat(path) if err == nil { @@ -52,7 +66,7 @@ func getOperationAndCapabilityFromName(capName string) (op, capability string) { switch strings.ToLower(capName) { case "net_raw": op = "Network" - capability = "SOCK_RAW" + capability = "raw" // we will remove this when we have proper handling of capabilities default: return "", "unknown" } @@ -185,7 +199,7 @@ func (fd *Feeder) newMatchPolicy(policyEnabled int, policyName, src string, mp i match.Message = npt.Message match.Operation = "Network" - match.Resource = GetProtocolFromName(npt.Protocol) + match.Resource = npt.Protocol match.ResourceType = "Protocol" if policyEnabled == tp.KubeArmorPolicyAudited && npt.Action == "Allow" { @@ -923,7 +937,6 @@ func setLogFields(log *tp.Log, existAllowPolicy bool, defaultPosture string, vis (*log).Type = "HostLog" } - // handles host visibility // return true if visibility enabled // return false otherwise so that log is skipped @@ -960,7 +973,10 @@ func (fd *Feeder) UpdateMatchedPolicy(log tp.Log) tp.Log { } secPolicies := fd.SecurityPolicies[key].Policies - for _, secPolicy := range secPolicies { + // for "Network" case below we use skip bool to skip the log when the log is matched with one of the allowed rules in secPolicies + // skip is set to true(in below cases, in Network) for the log event which is matched by the rules + skip := false + for rule, secPolicy := range secPolicies { if secPolicy.Action == "Allow" || secPolicy.Action == "Audit (Allow)" { if secPolicy.Operation == "Process" || secPolicy.Operation == "File" { existFileAllowPolicy = true @@ -1206,105 +1222,115 @@ func (fd *Feeder) UpdateMatchedPolicy(log tp.Log) tp.Log { continue } + // when one of the below rule is already matched for the log event, we will skip for further matches + if skip { + break // break, so that once source is matched for a log it doesn't look for other cases + } // match sources if (!secPolicy.IsFromSource) || (secPolicy.IsFromSource && (secPolicy.Source == log.ParentProcessName || secPolicy.Source == log.ProcessName)) { - skip := false - - for _, matchProtocol := range strings.Split(secPolicy.Resource, ",") { - if skip { - break - } - - // match resources - if strings.Contains(log.Resource, matchProtocol) { - if (secPolicy.Action == "Allow" || secPolicy.Action == "Audit (Allow)") && log.Result == "Passed" { - // allow policy or allow policy with audit mode - // matched source + matched resource + matched action + expected result -> going to be skipped + matchedFlags := false - log.Type = "MatchedPolicy" + protocol := fetchProtocol(log.Resource) + if protocol == secPolicy.Resource { + matchedFlags = true + } - log.PolicyName = secPolicy.PolicyName - log.Severity = secPolicy.Severity + if matchedFlags { + if (secPolicy.Action == "Allow" || secPolicy.Action == "Audit (Allow)") && log.Result == "Passed" { + // allow policy or allow policy with audit mode + // matched source + matched resource + matched action + expected result -> going to be skipped - if len(secPolicy.Tags) > 0 { - log.Tags = strings.Join(secPolicy.Tags[:], ",") - log.ATags = secPolicy.Tags - } + log.Type = "MatchedPolicy" - if len(secPolicy.Message) > 0 { - log.Message = secPolicy.Message - } + log.PolicyName = secPolicy.PolicyName + log.Severity = secPolicy.Severity - if log.PolicyEnabled == tp.KubeArmorPolicyAudited { - log.Enforcer = "eBPF Monitor" - } else { - log.Enforcer = fd.Enforcer - } + if len(secPolicy.Tags) > 0 { + log.Tags = strings.Join(secPolicy.Tags[:], ",") + log.ATags = secPolicy.Tags + } - log.Action = "Allow" + if len(secPolicy.Message) > 0 { + log.Message = secPolicy.Message + } - skip = true - continue + if log.PolicyEnabled == tp.KubeArmorPolicyAudited { + log.Enforcer = "eBPF Monitor" + } else { + log.Enforcer = fd.Enforcer } - if secPolicy.Action == "Audit" && log.Result == "Passed" { - // audit policy - // matched source + matched resource + matched action + expected result -> alert (audit log) + log.Action = "Allow" - log.Type = "MatchedPolicy" + skip = true + continue + } - log.PolicyName = secPolicy.PolicyName - log.Severity = secPolicy.Severity + if secPolicy.Action == "Audit" && log.Result == "Passed" { + // audit policy + // matched source + matched resource + matched action + expected result -> alert (audit log) - if len(secPolicy.Tags) > 0 { - log.Tags = strings.Join(secPolicy.Tags[:], ",") - log.ATags = secPolicy.Tags - } + log.Type = "MatchedPolicy" - if len(secPolicy.Message) > 0 { - log.Message = secPolicy.Message - } + log.PolicyName = secPolicy.PolicyName + log.Severity = secPolicy.Severity - log.Enforcer = "eBPF Monitor" - log.Action = secPolicy.Action + if len(secPolicy.Tags) > 0 { + log.Tags = strings.Join(secPolicy.Tags[:], ",") + log.ATags = secPolicy.Tags + } - skip = true - continue + if len(secPolicy.Message) > 0 { + log.Message = secPolicy.Message } - if (secPolicy.Action == "Block" && log.Result != "Passed") || - (secPolicy.Action == "Audit (Block)" && log.Result == "Passed") { - // block policy or block policy with audit mode - // matched source + matched resource + matched action + expected result -> alert + log.Enforcer = "eBPF Monitor" + log.Action = secPolicy.Action - log.Type = "MatchedPolicy" + skip = true + continue + } - log.PolicyName = secPolicy.PolicyName - log.Severity = secPolicy.Severity + if (secPolicy.Action == "Block" && log.Result != "Passed") || + (secPolicy.Action == "Audit (Block)" && log.Result == "Passed") { + // block policy or block policy with audit mode + // matched source + matched resource + matched action + expected result -> alert - if len(secPolicy.Tags) > 0 { - log.Tags = strings.Join(secPolicy.Tags[:], ",") - } + log.Type = "MatchedPolicy" - if len(secPolicy.Message) > 0 { - log.Message = secPolicy.Message - } + log.PolicyName = secPolicy.PolicyName + log.Severity = secPolicy.Severity - if log.PolicyEnabled == tp.KubeArmorPolicyAudited { - log.Enforcer = "eBPF Monitor" - } else { - log.Enforcer = fd.Enforcer - } + if len(secPolicy.Tags) > 0 { + log.Tags = strings.Join(secPolicy.Tags[:], ",") + } - log.Action = secPolicy.Action + if len(secPolicy.Message) > 0 { + log.Message = secPolicy.Message + } - skip = true - continue + if log.PolicyEnabled == tp.KubeArmorPolicyAudited { + log.Enforcer = "eBPF Monitor" + } else { + log.Enforcer = fd.Enforcer } + + log.Action = secPolicy.Action + + skip = true + continue } } + // if protocol is unknown we skip the audit alert event + if protocol == "unknown" { + log.Type = "MatchedPolicy" + log.Action = "Allow" + continue + } - if skip { + // keep looking for a rule to be matched + // send audit alert only when all the rules are compared and none is matched + if !matchedFlags && rule < len(secPolicies)-1 { continue } diff --git a/tests/k8s_env/block/block_test.go b/tests/k8s_env/block/block_test.go index 45d5cf0c57..ef9150f2ac 100644 --- a/tests/k8s_env/block/block_test.go +++ b/tests/k8s_env/block/block_test.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + "github.com/kubearmor/KubeArmor/tests/util" . "github.com/kubearmor/KubeArmor/tests/util" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -86,8 +87,10 @@ var _ = Describe("Posture", func() { }) It("can whitelist certain files accessed by a package while blocking all other sensitive content", func() { + err := util.AnnotateNS("wordpress-mysql", "kubearmor-network-posture", "block") + Expect(err).To(BeNil()) // Apply policy - err := K8sApplyFile("res/ksp-wordpress-allow-file.yaml") + err = K8sApplyFile("res/ksp-wordpress-allow-file.yaml") Expect(err).To(BeNil()) // wait for policy creation, added due to flaky behaviour diff --git a/tests/k8s_env/smoke/res/ksp-wordpress-allow-tcp.yaml b/tests/k8s_env/smoke/res/ksp-wordpress-allow-tcp.yaml new file mode 100644 index 0000000000..b25a723f78 --- /dev/null +++ b/tests/k8s_env/smoke/res/ksp-wordpress-allow-tcp.yaml @@ -0,0 +1,18 @@ +apiVersion: security.kubearmor.com/v1 +kind: KubeArmorPolicy +metadata: + name: ksp-wordpress-net-tcp-allow-curl + namespace: wordpress-mysql +spec: + severity: 8 + selector: + matchLabels: + app: wordpress + network: + matchProtocols: + - protocol: tcp + fromSource: + - path: /usr/bin/curl + - path: /bin/bash + action: + Allow \ No newline at end of file diff --git a/tests/k8s_env/smoke/smoke_test.go b/tests/k8s_env/smoke/smoke_test.go index 71ec7a360b..f2ce1e453c 100644 --- a/tests/k8s_env/smoke/smoke_test.go +++ b/tests/k8s_env/smoke/smoke_test.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + "github.com/kubearmor/KubeArmor/tests/util" . "github.com/kubearmor/KubeArmor/tests/util" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -287,6 +288,47 @@ var _ = Describe("Smoke", func() { Expect(alerts[0].PolicyName).To(Equal("ksp-wordpress-block-mount-file")) Expect(alerts[0].Severity).To(Equal("5")) }) + It("will allow use of tcp network protocol by curl and bash", func() { + err := util.AnnotateNS("wordpress-mysql", "kubearmor-network-posture", "audit") + Expect(err).To(BeNil()) + // Apply policy + err = K8sApplyFile("res/ksp-wordpress-allow-tcp.yaml") + Expect(err).To(BeNil()) + + // Start Kubearmor Logs + err = KarmorLogStart("policy", "wordpress-mysql", "Network", wp) + Expect(err).To(BeNil()) + + // wait for policy creation + time.Sleep(5 * time.Second) + + sout, _, err := K8sExecInPod(wp, "wordpress-mysql", + []string{"bash", "-c", "curl 142.250.193.46"}) + Expect(err).To(BeNil()) + fmt.Printf("OUTPUT: %s\n", sout) + // tcp action + Expect(sout).To(ContainSubstring("http://www.google.com/")) + + // check alert + _, alerts, err := KarmorGetLogs(5*time.Second, 1) + fmt.Printf("OUTPUT: %s\n", alerts) + Expect(err).To(BeNil()) + Expect(len(alerts)).To(Equal(0)) + + // tcp + udp + raw action + sout, _, err = K8sExecInPod(wp, "wordpress-mysql", + []string{"bash", "-c", "curl google.com"}) + Expect(err).To(BeNil()) + fmt.Printf("OUTPUT: %s\n", sout) + Expect(sout).To(ContainSubstring("http://www.google.com/")) + + // check alert + _, alerts, err = KarmorGetLogs(5*time.Second, 1) + Expect(err).To(BeNil()) + Expect(len(alerts)).To(BeNumerically(">=", 1)) + Expect(alerts[0].PolicyName).To(Equal("DefaultPosture")) + Expect(alerts[0].Result).To(Equal("Passed")) + }) }) })