From cad01c057b11a0a928eb714f4967c7a841ef35b2 Mon Sep 17 00:00:00 2001 From: Ankur Kothiwal <ankur.kothiwal@accuknox.com> Date: Fri, 20 May 2022 20:52:50 +0530 Subject: [PATCH 1/6] KubeArmor support for un-orchestrated containers Signed-off-by: Ankur Kothiwal <ankur.kothiwal@accuknox.com> --- KubeArmor/Makefile | 5 + KubeArmor/common/common.go | 2 +- KubeArmor/core/dockerHandler.go | 25 ++ KubeArmor/core/kubeArmor.go | 15 +- KubeArmor/core/kubeUpdate.go | 427 ++++++++++++++++++++++++ KubeArmor/enforcer/appArmorEnforcer.go | 1 - KubeArmor/policy/policy.go | 22 +- examples/kubearmor_containerpolicy.yaml | 18 + protobuf/policy.pb.go | 26 +- protobuf/policy.proto | 1 + protobuf/policy_grpc.pb.go | 38 ++- 11 files changed, 564 insertions(+), 16 deletions(-) create mode 100644 examples/kubearmor_containerpolicy.yaml diff --git a/KubeArmor/Makefile b/KubeArmor/Makefile index b44814974e..2f30e03dab 100644 --- a/KubeArmor/Makefile +++ b/KubeArmor/Makefile @@ -45,6 +45,11 @@ run: build cd $(CURDIR)/BPF; make cd $(CURDIR); sudo -E ./kubearmor -logPath=/tmp/kubearmor.log -enableKubeArmorPolicy=true -enableKubeArmorHostPolicy=true -hostVisibility=process,file,network,capabilities +.PHONY: run-container +run-container: build + cd $(CURDIR); sudo rm -f /tmp/kubearmor.log + cd $(CURDIR); sudo -E ./kubearmor -logPath=/tmp/kubearmor.log -enableKubeArmorHostPolicy -enableKubeArmorPolicy -k8s=false + .PHONY: run-host-only run-host-only: build cd $(CRDDIR); kubectl apply -f KubeArmorHostPolicy.yaml diff --git a/KubeArmor/common/common.go b/KubeArmor/common/common.go index 72ca1d41a4..1f034688e7 100644 --- a/KubeArmor/common/common.go +++ b/KubeArmor/common/common.go @@ -414,7 +414,7 @@ func MatchIdentities(identities []string, superIdentities []string) bool { return false } - // if super identities not include indentity, return false + // if super identities not include identity, return false for _, identity := range identities { if !ContainsElement(superIdentities, identity) { matched = false diff --git a/KubeArmor/core/dockerHandler.go b/KubeArmor/core/dockerHandler.go index 210dcb23e7..9447dfaf68 100644 --- a/KubeArmor/core/dockerHandler.go +++ b/KubeArmor/core/dockerHandler.go @@ -237,6 +237,18 @@ func (dm *KubeArmorDaemon) GetAlreadyDeployedDockerContainers() { continue } + // check for unorchestrated docker containers + if !dm.K8sEnabled { + dm.ContainersLock.Lock() + container.ProcessVisibilityEnabled = true + container.FileVisibilityEnabled = true + container.NetworkVisibilityEnabled = true + container.CapabilitiesVisibilityEnabled = true + + dm.Containers[container.ContainerID] = container + dm.ContainersLock.Unlock() + } + if dm.SystemMonitor != nil && cfg.GlobalCfg.Policy { // update NsMap dm.SystemMonitor.AddContainerIDToNsMap(container.ContainerID, container.PidNS, container.MntNS) @@ -318,6 +330,19 @@ func (dm *KubeArmorDaemon) UpdateDockerContainer(containerID, action string) { return } + if !dm.K8sEnabled { + dm.ContainersLock.Lock() + container.ProcessVisibilityEnabled = true + container.FileVisibilityEnabled = true + container.NetworkVisibilityEnabled = true + container.CapabilitiesVisibilityEnabled = true + container.EndPointName = container.ContainerName + container.NamespaceName = "container_namespace" + + dm.Containers[container.ContainerID] = container + dm.ContainersLock.Unlock() + } + if dm.SystemMonitor != nil && cfg.GlobalCfg.Policy { // update NsMap dm.SystemMonitor.AddContainerIDToNsMap(containerID, container.PidNS, container.MntNS) diff --git a/KubeArmor/core/kubeArmor.go b/KubeArmor/core/kubeArmor.go index 64c22367fc..7685d84af5 100644 --- a/KubeArmor/core/kubeArmor.go +++ b/KubeArmor/core/kubeArmor.go @@ -448,6 +448,11 @@ func KubeArmor() { // == // + if !dm.K8sEnabled && cfg.GlobalCfg.Policy { + dm.GetAlreadyDeployedDockerContainers() + go dm.MonitorDockerEvents() + } + if dm.K8sEnabled && cfg.GlobalCfg.Policy { // check if the CRI socket set while executing kubearmor exists if cfg.GlobalCfg.CRISocket != "" { @@ -577,8 +582,9 @@ func KubeArmor() { dm.Logger.Print("Started to monitor host security policies") } + policyService := &policy.ServiceServer{} + if !cfg.GlobalCfg.K8sEnv && cfg.GlobalCfg.HostPolicy { - policyService := &policy.ServiceServer{} policyService.UpdateHostPolicy = dm.ParseAndUpdateHostSecurityPolicy dm.Node.PolicyEnabled = tp.KubeArmorPolicyEnabled @@ -586,6 +592,13 @@ func KubeArmor() { reflection.Register(dm.Logger.LogServer) dm.Logger.Print("Started to monitor host security policies on gRPC") + + } + + if !dm.K8sEnabled && cfg.GlobalCfg.Policy { + policyService.UpdateContainerPolicy = dm.ParseAndUpdateContainerSecurityPolicy + + dm.Logger.Print("Started to monitor container security policies on gRPC") } // serve log feeds diff --git a/KubeArmor/core/kubeUpdate.go b/KubeArmor/core/kubeUpdate.go index cdcf750945..1fac34e8c4 100644 --- a/KubeArmor/core/kubeUpdate.go +++ b/KubeArmor/core/kubeUpdate.go @@ -1179,6 +1179,433 @@ func (dm *KubeArmorDaemon) WatchSecurityPolicies() { } } +// ====================================== // +// == Container Security Policy Update == // +// ====================================== // + +//ParseAndUpdateContainerSecurityPolicy Function +func (dm *KubeArmorDaemon) ParseAndUpdateContainerSecurityPolicy(event tp.K8sKubeArmorPolicyEvent) { + // create a container security policy + secPolicy := tp.SecurityPolicy{} + + secPolicy.Metadata = map[string]string{} + secPolicy.Metadata["namespaceName"] = "container_namespace" //event.Object.Metadata.Namespace + secPolicy.Metadata["policyName"] = event.Object.Metadata.Name + + if err := kl.Clone(event.Object.Spec, &secPolicy.Spec); err != nil { + dm.Logger.Errf("Failed to clone a spec (%s)", err.Error()) + return + } + + kl.ObjCommaExpandFirstDupOthers(&secPolicy.Spec.Network.MatchProtocols) + kl.ObjCommaExpandFirstDupOthers(&secPolicy.Spec.Capabilities.MatchCapabilities) + + if secPolicy.Spec.Severity == 0 { + secPolicy.Spec.Severity = 1 // the lowest severity, by default + } + + switch secPolicy.Spec.Action { + case "allow": + secPolicy.Spec.Action = "Allow" + case "audit": + secPolicy.Spec.Action = "Audit" + case "block": + secPolicy.Spec.Action = "Block" + case "": + secPolicy.Spec.Action = "Block" // by default + } + + // add identities + + secPolicy.Spec.Selector.Identities = []string{"namespaceName=" + event.Object.Metadata.Namespace} + containername := "" + for k, v := range secPolicy.Spec.Selector.MatchLabels { + secPolicy.Spec.Selector.Identities = append(secPolicy.Spec.Selector.Identities, k+"="+v) + containername = v // TODO: fix identity behavior + } + + sort.Slice(secPolicy.Spec.Selector.Identities, func(i, j int) bool { + return secPolicy.Spec.Selector.Identities[i] < secPolicy.Spec.Selector.Identities[j] + }) + + // add severities, tags, messages, and actions + + if len(secPolicy.Spec.Process.MatchPaths) > 0 { + for idx, path := range secPolicy.Spec.Process.MatchPaths { + if path.Severity == 0 { + if secPolicy.Spec.Process.Severity != 0 { + secPolicy.Spec.Process.MatchPaths[idx].Severity = secPolicy.Spec.Process.Severity + } else { + secPolicy.Spec.Process.MatchPaths[idx].Severity = secPolicy.Spec.Severity + } + } + + if len(path.Tags) == 0 { + if len(secPolicy.Spec.Process.Tags) > 0 { + secPolicy.Spec.Process.MatchPaths[idx].Tags = secPolicy.Spec.Process.Tags + } else { + secPolicy.Spec.Process.MatchPaths[idx].Tags = secPolicy.Spec.Tags + } + } + + if len(path.Message) == 0 { + if len(secPolicy.Spec.Process.Message) > 0 { + secPolicy.Spec.Process.MatchPaths[idx].Message = secPolicy.Spec.Process.Message + } else { + secPolicy.Spec.Process.MatchPaths[idx].Message = secPolicy.Spec.Message + } + } + + if len(path.Action) == 0 { + if len(secPolicy.Spec.Process.Action) > 0 { + secPolicy.Spec.Process.MatchPaths[idx].Action = secPolicy.Spec.Process.Action + } else { + secPolicy.Spec.Process.MatchPaths[idx].Action = secPolicy.Spec.Action + } + } + } + } + + if len(secPolicy.Spec.Process.MatchDirectories) > 0 { + for idx, dir := range secPolicy.Spec.Process.MatchDirectories { + if dir.Severity == 0 { + if secPolicy.Spec.Process.Severity != 0 { + secPolicy.Spec.Process.MatchDirectories[idx].Severity = secPolicy.Spec.Process.Severity + } else { + secPolicy.Spec.Process.MatchDirectories[idx].Severity = secPolicy.Spec.Severity + } + } + + if len(dir.Tags) == 0 { + if len(secPolicy.Spec.Process.Tags) > 0 { + secPolicy.Spec.Process.MatchDirectories[idx].Tags = secPolicy.Spec.Process.Tags + } else { + secPolicy.Spec.Process.MatchDirectories[idx].Tags = secPolicy.Spec.Tags + } + } + + if len(dir.Message) == 0 { + if len(secPolicy.Spec.Process.Message) > 0 { + secPolicy.Spec.Process.MatchDirectories[idx].Message = secPolicy.Spec.Process.Message + } else { + secPolicy.Spec.Process.MatchDirectories[idx].Message = secPolicy.Spec.Message + } + } + + if len(dir.Action) == 0 { + if len(secPolicy.Spec.Process.Action) > 0 { + secPolicy.Spec.Process.MatchDirectories[idx].Action = secPolicy.Spec.Process.Action + } else { + secPolicy.Spec.Process.MatchDirectories[idx].Action = secPolicy.Spec.Action + } + } + } + } + + if len(secPolicy.Spec.Process.MatchPatterns) > 0 { + for idx, pat := range secPolicy.Spec.Process.MatchPatterns { + if pat.Severity == 0 { + if secPolicy.Spec.Process.Severity != 0 { + secPolicy.Spec.Process.MatchPatterns[idx].Severity = secPolicy.Spec.Process.Severity + } else { + secPolicy.Spec.Process.MatchPatterns[idx].Severity = secPolicy.Spec.Severity + } + } + + if len(pat.Tags) == 0 { + if len(secPolicy.Spec.Process.Tags) > 0 { + secPolicy.Spec.Process.MatchPatterns[idx].Tags = secPolicy.Spec.Process.Tags + } else { + secPolicy.Spec.Process.MatchPatterns[idx].Tags = secPolicy.Spec.Tags + } + } + + if len(pat.Message) == 0 { + if len(secPolicy.Spec.Process.Message) > 0 { + secPolicy.Spec.Process.MatchPatterns[idx].Message = secPolicy.Spec.Process.Message + } else { + secPolicy.Spec.Process.MatchPatterns[idx].Message = secPolicy.Spec.Message + } + } + + if len(pat.Action) == 0 { + if len(secPolicy.Spec.Process.Action) > 0 { + secPolicy.Spec.Process.MatchPatterns[idx].Action = secPolicy.Spec.Process.Action + } else { + secPolicy.Spec.Process.MatchPatterns[idx].Action = secPolicy.Spec.Action + } + } + } + } + + if len(secPolicy.Spec.File.MatchPaths) > 0 { + for idx, path := range secPolicy.Spec.File.MatchPaths { + if path.Severity == 0 { + if secPolicy.Spec.File.Severity != 0 { + secPolicy.Spec.File.MatchPaths[idx].Severity = secPolicy.Spec.File.Severity + } else { + secPolicy.Spec.File.MatchPaths[idx].Severity = secPolicy.Spec.Severity + } + } + + if len(path.Tags) == 0 { + if len(secPolicy.Spec.File.Tags) > 0 { + secPolicy.Spec.File.MatchPaths[idx].Tags = secPolicy.Spec.File.Tags + } else { + secPolicy.Spec.File.MatchPaths[idx].Tags = secPolicy.Spec.Tags + } + } + + if len(path.Message) == 0 { + if len(secPolicy.Spec.File.Message) > 0 { + secPolicy.Spec.File.MatchPaths[idx].Message = secPolicy.Spec.File.Message + } else { + secPolicy.Spec.File.MatchPaths[idx].Message = secPolicy.Spec.Message + } + } + + if len(path.Action) == 0 { + if len(secPolicy.Spec.File.Action) > 0 { + secPolicy.Spec.File.MatchPaths[idx].Action = secPolicy.Spec.File.Action + } else { + secPolicy.Spec.File.MatchPaths[idx].Action = secPolicy.Spec.Action + } + } + } + } + + if len(secPolicy.Spec.File.MatchDirectories) > 0 { + for idx, dir := range secPolicy.Spec.File.MatchDirectories { + if dir.Severity == 0 { + if secPolicy.Spec.File.Severity != 0 { + secPolicy.Spec.File.MatchDirectories[idx].Severity = secPolicy.Spec.File.Severity + } else { + secPolicy.Spec.File.MatchDirectories[idx].Severity = secPolicy.Spec.Severity + } + } + + if len(dir.Tags) == 0 { + if len(secPolicy.Spec.File.Tags) > 0 { + secPolicy.Spec.File.MatchDirectories[idx].Tags = secPolicy.Spec.File.Tags + } else { + secPolicy.Spec.File.MatchDirectories[idx].Tags = secPolicy.Spec.Tags + } + } + + if len(dir.Message) == 0 { + if len(secPolicy.Spec.File.Message) > 0 { + secPolicy.Spec.File.MatchDirectories[idx].Message = secPolicy.Spec.File.Message + } else { + secPolicy.Spec.File.MatchDirectories[idx].Message = secPolicy.Spec.Message + } + } + + if len(dir.Action) == 0 { + if len(secPolicy.Spec.File.Action) > 0 { + secPolicy.Spec.File.MatchDirectories[idx].Action = secPolicy.Spec.File.Action + } else { + secPolicy.Spec.File.MatchDirectories[idx].Action = secPolicy.Spec.Action + } + } + } + } + + if len(secPolicy.Spec.File.MatchPatterns) > 0 { + for idx, pat := range secPolicy.Spec.File.MatchPatterns { + if pat.Severity == 0 { + if secPolicy.Spec.File.Severity != 0 { + secPolicy.Spec.File.MatchPatterns[idx].Severity = secPolicy.Spec.File.Severity + } else { + secPolicy.Spec.File.MatchPatterns[idx].Severity = secPolicy.Spec.Severity + } + } + + if len(pat.Tags) == 0 { + if len(secPolicy.Spec.File.Tags) > 0 { + secPolicy.Spec.File.MatchPatterns[idx].Tags = secPolicy.Spec.File.Tags + } else { + secPolicy.Spec.File.MatchPatterns[idx].Tags = secPolicy.Spec.Tags + } + } + + if len(pat.Message) == 0 { + if len(secPolicy.Spec.File.Message) > 0 { + secPolicy.Spec.File.MatchPatterns[idx].Message = secPolicy.Spec.File.Message + } else { + secPolicy.Spec.File.MatchPatterns[idx].Message = secPolicy.Spec.Message + } + } + + if len(pat.Action) == 0 { + if len(secPolicy.Spec.File.Action) > 0 { + secPolicy.Spec.File.MatchPatterns[idx].Action = secPolicy.Spec.File.Action + } else { + secPolicy.Spec.File.MatchPatterns[idx].Action = secPolicy.Spec.Action + } + } + } + } + + if len(secPolicy.Spec.Network.MatchProtocols) > 0 { + for idx, proto := range secPolicy.Spec.Network.MatchProtocols { + if proto.Severity == 0 { + if secPolicy.Spec.Network.Severity != 0 { + secPolicy.Spec.Network.MatchProtocols[idx].Severity = secPolicy.Spec.Network.Severity + } else { + secPolicy.Spec.Network.MatchProtocols[idx].Severity = secPolicy.Spec.Severity + } + } + + if len(proto.Tags) == 0 { + if len(secPolicy.Spec.Network.Tags) > 0 { + secPolicy.Spec.Network.MatchProtocols[idx].Tags = secPolicy.Spec.Network.Tags + } else { + secPolicy.Spec.Network.MatchProtocols[idx].Tags = secPolicy.Spec.Tags + } + } + + if len(proto.Message) == 0 { + if len(secPolicy.Spec.Network.Message) > 0 { + secPolicy.Spec.Network.MatchProtocols[idx].Message = secPolicy.Spec.Network.Message + } else { + secPolicy.Spec.Network.MatchProtocols[idx].Message = secPolicy.Spec.Message + } + } + + if len(proto.Action) == 0 { + if len(secPolicy.Spec.Network.Action) > 0 { + secPolicy.Spec.Network.MatchProtocols[idx].Action = secPolicy.Spec.Network.Action + } else { + secPolicy.Spec.Network.MatchProtocols[idx].Action = secPolicy.Spec.Action + } + } + } + } + + if len(secPolicy.Spec.Capabilities.MatchCapabilities) > 0 { + for idx, cap := range secPolicy.Spec.Capabilities.MatchCapabilities { + if cap.Severity == 0 { + if secPolicy.Spec.Capabilities.Severity != 0 { + secPolicy.Spec.Capabilities.MatchCapabilities[idx].Severity = secPolicy.Spec.Capabilities.Severity + } else { + secPolicy.Spec.Capabilities.MatchCapabilities[idx].Severity = secPolicy.Spec.Severity + } + } + + if len(cap.Tags) == 0 { + if len(secPolicy.Spec.Capabilities.Tags) > 0 { + secPolicy.Spec.Capabilities.MatchCapabilities[idx].Tags = secPolicy.Spec.Capabilities.Tags + } else { + secPolicy.Spec.Capabilities.MatchCapabilities[idx].Tags = secPolicy.Spec.Tags + } + } + + if len(cap.Message) == 0 { + if len(secPolicy.Spec.Capabilities.Message) > 0 { + secPolicy.Spec.Capabilities.MatchCapabilities[idx].Message = secPolicy.Spec.Capabilities.Message + } else { + secPolicy.Spec.Capabilities.MatchCapabilities[idx].Message = secPolicy.Spec.Message + } + } + + if len(cap.Action) == 0 { + if len(secPolicy.Spec.Capabilities.Action) > 0 { + secPolicy.Spec.Capabilities.MatchCapabilities[idx].Action = secPolicy.Spec.Capabilities.Action + } else { + secPolicy.Spec.Capabilities.MatchCapabilities[idx].Action = secPolicy.Spec.Action + } + } + } + } + + dm.Logger.Printf("Detected a Container Security Policy (%s/%s/%s)", strings.ToLower(event.Type), secPolicy.Metadata["namespaceName"], secPolicy.Metadata["policyName"]) + + appArmorAnnotations := map[string]string{} + appArmorAnnotations[containername] = "kubearmor_" + containername + + newPoint := tp.EndPoint{} + + i := -1 + + for idx, endPoint := range dm.EndPoints { + if kl.MatchIdentities(secPolicy.Spec.Selector.Identities, endPoint.Identities) { + i = idx + newPoint = endPoint + break + } + } + + for idx, policy := range newPoint.SecurityPolicies { + if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { + event.Type = "MODIFIED" + // Policy already exists so it will be modified + newPoint.SecurityPolicies[idx] = secPolicy + } + } + + if event.Type == "ADDED" { + dm.RuntimeEnforcer.UpdateAppArmorProfiles(containername, "ADDED", appArmorAnnotations) + + newPoint.SecurityPolicies = append(newPoint.SecurityPolicies, secPolicy) + if i < 0 { + // Create new EndPoint + newPoint.NamespaceName = secPolicy.Metadata["namespaceName"] + newPoint.EndPointName = containername + newPoint.PolicyEnabled = tp.KubeArmorPolicyEnabled + newPoint.Identities = secPolicy.Spec.Selector.Identities + + newPoint.ProcessVisibilityEnabled = true + newPoint.FileVisibilityEnabled = true + newPoint.NetworkVisibilityEnabled = true + newPoint.CapabilitiesVisibilityEnabled = true + + newPoint.Containers = []string{} + newPoint.AppArmorProfiles = []string{"kubearmor_" + containername} + + // add the endpoint into the endpoint list + dm.EndPoints = append(dm.EndPoints, newPoint) + } else { + dm.EndPoints[i] = newPoint + } + + if cfg.GlobalCfg.Policy { + // update security policies + dm.Logger.UpdateSecurityPolicies("ADDED", newPoint) + + if dm.RuntimeEnforcer != nil && newPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce security policies + dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) + } + } + } else if event.Type == "MODIFIED" { + dm.EndPoints[i] = newPoint + if cfg.GlobalCfg.Policy { + // update security policies + dm.Logger.UpdateSecurityPolicies("MODIFIED", newPoint) + + if dm.RuntimeEnforcer != nil && newPoint.PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce security policies + dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) + } + } + } else { + // update security policies + dm.Logger.UpdateSecurityPolicies("DELETED", newPoint) + + dm.RuntimeEnforcer.UpdateAppArmorProfiles(containername, "DELETED", appArmorAnnotations) + + dm.EndPointsLock.Lock() + for idx, endPoint := range dm.EndPoints { + if secPolicy.Metadata["namespaceName"] == endPoint.NamespaceName && secPolicy.Metadata["podName"] == endPoint.EndPointName { + // remove endpoint + dm.EndPoints = append(dm.EndPoints[:idx], dm.EndPoints[idx+1:]...) + break + } + } + dm.EndPointsLock.Unlock() + } +} + // ================================= // // == Host Security Policy Update == // // ================================= // diff --git a/KubeArmor/enforcer/appArmorEnforcer.go b/KubeArmor/enforcer/appArmorEnforcer.go index d7ee3d8843..05b1ddb49e 100644 --- a/KubeArmor/enforcer/appArmorEnforcer.go +++ b/KubeArmor/enforcer/appArmorEnforcer.go @@ -201,7 +201,6 @@ func (ae *AppArmorEnforcer) RegisterAppArmorProfile(podName, profileName string) return false } } - if _, ok := ae.AppArmorProfiles[profileName]; ok { if !kl.ContainsElement(ae.AppArmorProfiles[profileName], podName) { ae.AppArmorProfiles[profileName] = append(ae.AppArmorProfiles[profileName], podName) diff --git a/KubeArmor/policy/policy.go b/KubeArmor/policy/policy.go index 8685061c0c..ec8a8a32bd 100644 --- a/KubeArmor/policy/policy.go +++ b/KubeArmor/policy/policy.go @@ -15,10 +15,11 @@ import ( // ServiceServer provides structure to serve Policy gRPC service type ServiceServer struct { pb.PolicyServiceServer - UpdateHostPolicy func(tp.K8sKubeArmorHostPolicyEvent) + UpdateHostPolicy func(tp.K8sKubeArmorHostPolicyEvent) + UpdateContainerPolicy func(tp.K8sKubeArmorPolicyEvent) } -// HostPolicy accepts host policy event on gRPC service and updates host security polcies. It responds with 1 if success else 0. +// HostPolicy accepts host policy event on gRPC service and updates host security policies. It responds with 1 if success else 0. func (p *ServiceServer) HostPolicy(c context.Context, data *pb.Policy) (*pb.Response, error) { policyEvent := tp.K8sKubeArmorHostPolicyEvent{} res := new(pb.Response) @@ -34,3 +35,20 @@ func (p *ServiceServer) HostPolicy(c context.Context, data *pb.Policy) (*pb.Resp return res, nil } + +// ContainerPolicy accepts container events on gRPC and update container security policies +func (p *ServiceServer) ContainerPolicy(c context.Context, data *pb.Policy) (*pb.Response, error) { + policyEvent := tp.K8sKubeArmorPolicyEvent{} + res := new(pb.Response) + + err := json.Unmarshal(data.Policy, &policyEvent) + if err == nil { + p.UpdateContainerPolicy(policyEvent) + res.Status = 1 + } else { + kg.Warn("Invalid Container Policy Event") + res.Status = 0 + } + + return res, nil +} diff --git a/examples/kubearmor_containerpolicy.yaml b/examples/kubearmor_containerpolicy.yaml new file mode 100644 index 0000000000..e1ad8a8251 --- /dev/null +++ b/examples/kubearmor_containerpolicy.yaml @@ -0,0 +1,18 @@ +apiVersion: security.kubearmor.com/v1 +kind: KubeArmorPolicy +metadata: + name: process-block +spec: + severity: 5 + message: "a critical file was accessed" + tags: + - WARNING + selector: + matchLabels: + kubearmor.io/container.name: lb + process: + matchPaths: + - path: /usr/bin/ls + - path: /usr/bin/sleep + action: + Block diff --git a/protobuf/policy.pb.go b/protobuf/policy.pb.go index 7389efb899..c0305dff5e 100644 --- a/protobuf/policy.pb.go +++ b/protobuf/policy.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.4 +// protoc-gen-go v1.28.0 +// protoc v3.14.0 // source: policy.proto package protobuf @@ -122,14 +122,18 @@ var file_policy_proto_rawDesc = []byte{ 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x20, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x32, 0x3f, 0x0a, 0x0d, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x32, 0x74, 0x0a, 0x0d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x1a, 0x10, 0x2e, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x29, 0x5a, - 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x75, 0x62, 0x65, - 0x61, 0x72, 0x6d, 0x6f, 0x72, 0x2f, 0x4b, 0x75, 0x62, 0x65, 0x41, 0x72, 0x6d, 0x6f, 0x72, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, + 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x12, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x1a, 0x10, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x61, 0x72, 0x6d, 0x6f, 0x72, 0x2f, 0x4b, 0x75, 0x62, 0x65, 0x41, + 0x72, 0x6d, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -151,9 +155,11 @@ var file_policy_proto_goTypes = []interface{}{ } var file_policy_proto_depIdxs = []int32{ 1, // 0: policy.PolicyService.hostPolicy:input_type -> policy.policy - 0, // 1: policy.PolicyService.hostPolicy:output_type -> policy.response - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type + 1, // 1: policy.PolicyService.containerPolicy:input_type -> policy.policy + 0, // 2: policy.PolicyService.hostPolicy:output_type -> policy.response + 0, // 3: policy.PolicyService.containerPolicy:output_type -> policy.response + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name diff --git a/protobuf/policy.proto b/protobuf/policy.proto index 0c74e6a235..d04c7c8b2a 100644 --- a/protobuf/policy.proto +++ b/protobuf/policy.proto @@ -14,4 +14,5 @@ message policy { service PolicyService { rpc hostPolicy (policy) returns (response); + rpc containerPolicy (policy) returns (response); } diff --git a/protobuf/policy_grpc.pb.go b/protobuf/policy_grpc.pb.go index f92fbc257e..6fad8f4cf8 100644 --- a/protobuf/policy_grpc.pb.go +++ b/protobuf/policy_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v3.19.4 +// - protoc v3.14.0 // source: policy.proto package protobuf @@ -23,6 +23,7 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type PolicyServiceClient interface { HostPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) + ContainerPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) } type policyServiceClient struct { @@ -42,11 +43,21 @@ func (c *policyServiceClient) HostPolicy(ctx context.Context, in *Policy, opts . return out, nil } +func (c *policyServiceClient) ContainerPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/policy.PolicyService/containerPolicy", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // PolicyServiceServer is the server API for PolicyService service. // All implementations should embed UnimplementedPolicyServiceServer // for forward compatibility type PolicyServiceServer interface { HostPolicy(context.Context, *Policy) (*Response, error) + ContainerPolicy(context.Context, *Policy) (*Response, error) } // UnimplementedPolicyServiceServer should be embedded to have forward compatible implementations. @@ -56,6 +67,9 @@ type UnimplementedPolicyServiceServer struct { func (UnimplementedPolicyServiceServer) HostPolicy(context.Context, *Policy) (*Response, error) { return nil, status.Errorf(codes.Unimplemented, "method HostPolicy not implemented") } +func (UnimplementedPolicyServiceServer) ContainerPolicy(context.Context, *Policy) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method ContainerPolicy not implemented") +} // UnsafePolicyServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PolicyServiceServer will @@ -86,6 +100,24 @@ func _PolicyService_HostPolicy_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _PolicyService_ContainerPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Policy) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PolicyServiceServer).ContainerPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/policy.PolicyService/containerPolicy", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PolicyServiceServer).ContainerPolicy(ctx, req.(*Policy)) + } + return interceptor(ctx, in, info, handler) +} + // PolicyService_ServiceDesc is the grpc.ServiceDesc for PolicyService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -97,6 +129,10 @@ var PolicyService_ServiceDesc = grpc.ServiceDesc{ MethodName: "hostPolicy", Handler: _PolicyService_HostPolicy_Handler, }, + { + MethodName: "containerPolicy", + Handler: _PolicyService_ContainerPolicy_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "policy.proto", From 7937986f6f482a77d700dac170f8498466d5e783 Mon Sep 17 00:00:00 2001 From: Ankur Kothiwal <ankur.kothiwal@accuknox.com> Date: Mon, 11 Jul 2022 09:10:25 +0530 Subject: [PATCH 2/6] fix policy deletion Signed-off-by: Ankur Kothiwal <ankur.kothiwal@accuknox.com> --- KubeArmor/core/kubeUpdate.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/KubeArmor/core/kubeUpdate.go b/KubeArmor/core/kubeUpdate.go index 1fac34e8c4..595cd60dc8 100644 --- a/KubeArmor/core/kubeUpdate.go +++ b/KubeArmor/core/kubeUpdate.go @@ -1536,10 +1536,12 @@ func (dm *KubeArmorDaemon) ParseAndUpdateContainerSecurityPolicy(event tp.K8sKub } for idx, policy := range newPoint.SecurityPolicies { - if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { - event.Type = "MODIFIED" - // Policy already exists so it will be modified - newPoint.SecurityPolicies[idx] = secPolicy + if event.Type != "DELETED" { + if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { + event.Type = "MODIFIED" + // Policy already exists so it will be modified + newPoint.SecurityPolicies[idx] = secPolicy + } } } From ae7eb4819b686dd8b35d36f7676f93eb680cd4d8 Mon Sep 17 00:00:00 2001 From: Ankur Kothiwal <ankur.kothiwal@accuknox.com> Date: Mon, 11 Jul 2022 19:55:21 +0530 Subject: [PATCH 3/6] add default posture for containerized workloads Signed-off-by: Ankur Kothiwal <ankur.kothiwal@accuknox.com> --- KubeArmor/core/kubeUpdate.go | 33 ++++++++++++++------------ KubeArmor/enforcer/appArmorEnforcer.go | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/KubeArmor/core/kubeUpdate.go b/KubeArmor/core/kubeUpdate.go index 595cd60dc8..80af1e4da6 100644 --- a/KubeArmor/core/kubeUpdate.go +++ b/KubeArmor/core/kubeUpdate.go @@ -1535,11 +1535,23 @@ func (dm *KubeArmorDaemon) ParseAndUpdateContainerSecurityPolicy(event tp.K8sKub } } + globalDefaultPosture := tp.DefaultPosture{ + FileAction: cfg.GlobalCfg.DefaultFilePosture, + NetworkAction: cfg.GlobalCfg.DefaultNetworkPosture, + CapabilitiesAction: cfg.GlobalCfg.DefaultCapabilitiesPosture, + } + newPoint.DefaultPosture = globalDefaultPosture + for idx, policy := range newPoint.SecurityPolicies { - if event.Type != "DELETED" { + if event.Type == "DELETED" { + if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { + newPoint.SecurityPolicies = append(newPoint.SecurityPolicies[:idx], newPoint.SecurityPolicies[idx+1:]...) + break + } + } else { if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { event.Type = "MODIFIED" - // Policy already exists so it will be modified + // Policy already exists so modify newPoint.SecurityPolicies[idx] = secPolicy } } @@ -1590,21 +1602,12 @@ func (dm *KubeArmorDaemon) ParseAndUpdateContainerSecurityPolicy(event tp.K8sKub dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) } } - } else { - // update security policies + } else { // DELETED + // update security policies after policy deletion dm.Logger.UpdateSecurityPolicies("DELETED", newPoint) - dm.RuntimeEnforcer.UpdateAppArmorProfiles(containername, "DELETED", appArmorAnnotations) - - dm.EndPointsLock.Lock() - for idx, endPoint := range dm.EndPoints { - if secPolicy.Metadata["namespaceName"] == endPoint.NamespaceName && secPolicy.Metadata["podName"] == endPoint.EndPointName { - // remove endpoint - dm.EndPoints = append(dm.EndPoints[:idx], dm.EndPoints[idx+1:]...) - break - } - } - dm.EndPointsLock.Unlock() + dm.EndPoints[i] = newPoint + dm.RuntimeEnforcer.UpdateSecurityPolicies(newPoint) } } diff --git a/KubeArmor/enforcer/appArmorEnforcer.go b/KubeArmor/enforcer/appArmorEnforcer.go index 05b1ddb49e..c7e8f44916 100644 --- a/KubeArmor/enforcer/appArmorEnforcer.go +++ b/KubeArmor/enforcer/appArmorEnforcer.go @@ -277,7 +277,7 @@ func (ae *AppArmorEnforcer) UnregisterAppArmorProfile(podName, profileName strin ae.Logger.Warnf("Unable to read the AppArmor profile (%s, %s)", profileName, err.Error()) return false } else if !strings.Contains(string(content), "KubeArmor") { - ae.Logger.Warnf("Unabale to unregister the AppArmor profile (%s) (out-of-control)", profileName) + ae.Logger.Warnf("Unable to unregister the AppArmor profile (%s) (out-of-control)", profileName) return false } From f01114565f90876aceb8f4d3a920be49f3d464c9 Mon Sep 17 00:00:00 2001 From: Ankur Kothiwal <ankur.kothiwal@accuknox.com> Date: Mon, 1 Aug 2022 12:08:03 +0530 Subject: [PATCH 4/6] validate if a policy exist before delete operation Signed-off-by: Ankur Kothiwal <ankur.kothiwal@accuknox.com> --- KubeArmor/core/kubeUpdate.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/KubeArmor/core/kubeUpdate.go b/KubeArmor/core/kubeUpdate.go index 80af1e4da6..e7d874d0ad 100644 --- a/KubeArmor/core/kubeUpdate.go +++ b/KubeArmor/core/kubeUpdate.go @@ -1542,14 +1542,27 @@ func (dm *KubeArmorDaemon) ParseAndUpdateContainerSecurityPolicy(event tp.K8sKub } newPoint.DefaultPosture = globalDefaultPosture + // check that a security policy should exist before performing delete operation + policymatch := 0 + for _, policy := range newPoint.SecurityPolicies { + // check if policy exist + if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { + policymatch = 1 // policy exists + } + } + + // policy doesn't exist and the policy is being removed + if policymatch == 0 && event.Type == "DELETED" { + dm.Logger.Warnf("Failed to delete security policy. Policy doesn't exist") + return + } + for idx, policy := range newPoint.SecurityPolicies { - if event.Type == "DELETED" { - if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { + if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { + if event.Type == "DELETED" { newPoint.SecurityPolicies = append(newPoint.SecurityPolicies[:idx], newPoint.SecurityPolicies[idx+1:]...) break - } - } else { - if policy.Metadata["namespaceName"] == secPolicy.Metadata["namespaceName"] && policy.Metadata["policyName"] == secPolicy.Metadata["policyName"] { + } else { event.Type = "MODIFIED" // Policy already exists so modify newPoint.SecurityPolicies[idx] = secPolicy From e86d1a3eecaf7884f623f2aa54b6f50c94eb340a Mon Sep 17 00:00:00 2001 From: Ankur Kothiwal <ankur.kothiwal@accuknox.com> Date: Wed, 10 Aug 2022 02:42:32 +0530 Subject: [PATCH 5/6] auto detect container runtime and add crio support Signed-off-by: Ankur Kothiwal <ankur.kothiwal@accuknox.com> --- KubeArmor/core/kubeArmor.go | 62 +++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/KubeArmor/core/kubeArmor.go b/KubeArmor/core/kubeArmor.go index 7685d84af5..e13f394a10 100644 --- a/KubeArmor/core/kubeArmor.go +++ b/KubeArmor/core/kubeArmor.go @@ -311,6 +311,29 @@ func GetOSSigChannel() chan os.Signal { return c } +// ======================= // +// == Container runtime == // +// ======================= // + +// host container runtime available in case of containerized workloads +func hostContainerRuntime() (string, string) { + var sockFile, sockPath string + if _, err := os.Stat("/var/run/docker.sock"); err == nil { + sockFile = "docker" + sockPath = "/var/run/docker.sock" + } else if _, err := os.Stat("/var/run/containerd/containerd.sock"); err == nil { + sockFile = "containerd" + sockPath = "/var/run/containerd/containerd.sock" + } else if _, err := os.Stat("/var/run/crio/crio.sock"); err == nil { + sockFile = "crio" + sockPath = "/var/run/crio/crio.sock" + } else { + sockFile = "unavailable" + sockPath = "unavailable" + } + return sockFile, sockPath +} + // ========== // // == Main == // // ========== // @@ -412,7 +435,9 @@ func KubeArmor() { // == // + // Containerized workloads with Host if cfg.GlobalCfg.Policy || cfg.GlobalCfg.HostPolicy { + // initialize system monitor if !dm.InitSystemMonitor() { dm.Logger.Err("Failed to initialize KubeArmor Monitor") @@ -449,8 +474,41 @@ func KubeArmor() { // == // if !dm.K8sEnabled && cfg.GlobalCfg.Policy { - dm.GetAlreadyDeployedDockerContainers() - go dm.MonitorDockerEvents() + // check if the CRI socket set while executing kubearmor exists + if cfg.GlobalCfg.CRISocket != "" { + trimmedSocket := strings.TrimPrefix(cfg.GlobalCfg.CRISocket, "unix://") + if _, err := os.Stat(trimmedSocket); err != nil { + dm.Logger.Warnf("Error while looking for CRI socket file: %s", err.Error()) + } + } + cri, criPath := hostContainerRuntime() + if cri == "docker" { + if cfg.GlobalCfg.CRISocket == "" { + cfg.GlobalCfg.CRISocket = "unix://" + criPath + } + // update already deployed containers + dm.GetAlreadyDeployedDockerContainers() + + // monitor docker events + go dm.MonitorDockerEvents() + + } else if cri == "containerd" { + if cfg.GlobalCfg.CRISocket == "" { + cfg.GlobalCfg.CRISocket = "unix://" + criPath + } + // monitor containerd events + go dm.MonitorContainerdEvents() + + } else if cri == "crio" { + if cfg.GlobalCfg.CRISocket == "" { + cfg.GlobalCfg.CRISocket = "unix://" + criPath + } + // monitor cri-o events + go dm.MonitorCrioEvents() + + } else if cri == "unavailable" { + dm.Logger.Warnf("Failed to monitor containers. Unsupported container runtime") + } } if dm.K8sEnabled && cfg.GlobalCfg.Policy { From d51fcf9b25861fc6cbc011ce9f1d2ef5d4187cf2 Mon Sep 17 00:00:00 2001 From: Ankur Kothiwal <ankur.kothiwal@accuknox.com> Date: Thu, 11 Aug 2022 20:13:57 +0530 Subject: [PATCH 6/6] address review comments Signed-off-by: Ankur Kothiwal <ankur.kothiwal@accuknox.com> --- KubeArmor/Makefile | 4 +- KubeArmor/common/common.go | 12 +++-- KubeArmor/core/dockerHandler.go | 44 ++++++++++----- KubeArmor/core/kubeArmor.go | 94 ++++++++++++--------------------- KubeArmor/policy/policy.go | 22 ++++---- protobuf/policy.pb.go | 16 +++--- protobuf/policy.proto | 2 +- protobuf/policy_grpc.pb.go | 42 +++++++-------- 8 files changed, 116 insertions(+), 120 deletions(-) diff --git a/KubeArmor/Makefile b/KubeArmor/Makefile index 2f30e03dab..1d33e58f14 100644 --- a/KubeArmor/Makefile +++ b/KubeArmor/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# Copyright 2021 Authors of KubeArmor +# Copyright 2022 Authors of KubeArmor CURDIR := $(shell pwd) CRDDIR := $(realpath $(CURDIR)/../deployments/CRD) @@ -43,7 +43,7 @@ run: build cd $(CURDIR); sudo rm -f /tmp/kubearmor.log cd $(CURDIR)/BPF; make clean cd $(CURDIR)/BPF; make - cd $(CURDIR); sudo -E ./kubearmor -logPath=/tmp/kubearmor.log -enableKubeArmorPolicy=true -enableKubeArmorHostPolicy=true -hostVisibility=process,file,network,capabilities + cd $(CURDIR); sudo -E ./kubearmor -logPath=/tmp/kubearmor.log -enableKubeArmorPolicy -enableKubeArmorHostPolicy -hostVisibility=process,file,network,capabilities .PHONY: run-container run-container: build diff --git a/KubeArmor/common/common.go b/KubeArmor/common/common.go index 1f034688e7..5598bdf8fd 100644 --- a/KubeArmor/common/common.go +++ b/KubeArmor/common/common.go @@ -383,9 +383,15 @@ var ContainerRuntimeSocketMap = map[string][]string{ // GetCRISocket Function func GetCRISocket(ContainerRuntime string) string { - for _, candidate := range ContainerRuntimeSocketMap[ContainerRuntime] { - if _, err := os.Stat(candidate); err == nil { - return candidate + for k := range ContainerRuntimeSocketMap { + if ContainerRuntime != "" && k != ContainerRuntime { + continue + } + criruntime := k + for _, candidate := range ContainerRuntimeSocketMap[criruntime] { + if _, err := os.Stat(candidate); err == nil { + return candidate + } } } return "" diff --git a/KubeArmor/core/dockerHandler.go b/KubeArmor/core/dockerHandler.go index 9447dfaf68..62102bd88f 100644 --- a/KubeArmor/core/dockerHandler.go +++ b/KubeArmor/core/dockerHandler.go @@ -170,6 +170,34 @@ func (dh *DockerHandler) GetEventChannel() <-chan events.Message { // == Docker Events == // // =================== // +// Enable visibility flag arguments for un-orchestrated container +func (dm *KubeArmorDaemon) SetContainerVisibility(containerID string) { + + // get container information from docker client + container, err := Docker.GetContainerInfo(containerID) + if err != nil { + return + } + + if strings.Contains(cfg.GlobalCfg.Visibility, "process") { + container.ProcessVisibilityEnabled = true + } + if strings.Contains(cfg.GlobalCfg.Visibility, "file") { + container.FileVisibilityEnabled = true + } + if strings.Contains(cfg.GlobalCfg.Visibility, "network") { + container.NetworkVisibilityEnabled = true + } + if strings.Contains(cfg.GlobalCfg.Visibility, "capabilities") { + container.CapabilitiesVisibilityEnabled = true + } + + dm.Containers[container.ContainerID] = container + + container.EndPointName = container.ContainerName + container.NamespaceName = "container_namespace" +} + // GetAlreadyDeployedDockerContainers Function func (dm *KubeArmorDaemon) GetAlreadyDeployedDockerContainers() { // check if Docker exists else instantiate @@ -240,12 +268,7 @@ func (dm *KubeArmorDaemon) GetAlreadyDeployedDockerContainers() { // check for unorchestrated docker containers if !dm.K8sEnabled { dm.ContainersLock.Lock() - container.ProcessVisibilityEnabled = true - container.FileVisibilityEnabled = true - container.NetworkVisibilityEnabled = true - container.CapabilitiesVisibilityEnabled = true - - dm.Containers[container.ContainerID] = container + dm.SetContainerVisibility(dcontainer.ID) dm.ContainersLock.Unlock() } @@ -332,14 +355,7 @@ func (dm *KubeArmorDaemon) UpdateDockerContainer(containerID, action string) { if !dm.K8sEnabled { dm.ContainersLock.Lock() - container.ProcessVisibilityEnabled = true - container.FileVisibilityEnabled = true - container.NetworkVisibilityEnabled = true - container.CapabilitiesVisibilityEnabled = true - container.EndPointName = container.ContainerName - container.NamespaceName = "container_namespace" - - dm.Containers[container.ContainerID] = container + dm.SetContainerVisibility(containerID) dm.ContainersLock.Unlock() } diff --git a/KubeArmor/core/kubeArmor.go b/KubeArmor/core/kubeArmor.go index e13f394a10..b8287d5d7a 100644 --- a/KubeArmor/core/kubeArmor.go +++ b/KubeArmor/core/kubeArmor.go @@ -1,5 +1,5 @@ // SPDX-License-Identifier: Apache-2.0 -// Copyright 2021 Authors of KubeArmor +// Copyright 2022 Authors of KubeArmor package core @@ -311,27 +311,10 @@ func GetOSSigChannel() chan os.Signal { return c } -// ======================= // -// == Container runtime == // -// ======================= // - -// host container runtime available in case of containerized workloads -func hostContainerRuntime() (string, string) { - var sockFile, sockPath string - if _, err := os.Stat("/var/run/docker.sock"); err == nil { - sockFile = "docker" - sockPath = "/var/run/docker.sock" - } else if _, err := os.Stat("/var/run/containerd/containerd.sock"); err == nil { - sockFile = "containerd" - sockPath = "/var/run/containerd/containerd.sock" - } else if _, err := os.Stat("/var/run/crio/crio.sock"); err == nil { - sockFile = "crio" - sockPath = "/var/run/crio/crio.sock" - } else { - sockFile = "unavailable" - sockPath = "unavailable" - } - return sockFile, sockPath +// Trim sock file location from the env variable +func trimmedSocketPath() string { + trimmedPath := strings.TrimPrefix(cfg.GlobalCfg.CRISocket, "unix://") + return trimmedPath } // ========== // @@ -437,7 +420,6 @@ func KubeArmor() { // Containerized workloads with Host if cfg.GlobalCfg.Policy || cfg.GlobalCfg.HostPolicy { - // initialize system monitor if !dm.InitSystemMonitor() { dm.Logger.Err("Failed to initialize KubeArmor Monitor") @@ -453,8 +435,6 @@ func KubeArmor() { go dm.MonitorSystemEvents() dm.Logger.Print("Started to monitor system events") - // == // - // initialize runtime enforcer if !dm.InitRuntimeEnforcer() { dm.Logger.Print("Disabled KubeArmor Enforcer since No LSM is enabled") @@ -471,50 +451,44 @@ func KubeArmor() { } } - // == // + trimmedSocket := trimmedSocketPath() + // Un-orchestrated workloads if !dm.K8sEnabled && cfg.GlobalCfg.Policy { - // check if the CRI socket set while executing kubearmor exists - if cfg.GlobalCfg.CRISocket != "" { - trimmedSocket := strings.TrimPrefix(cfg.GlobalCfg.CRISocket, "unix://") - if _, err := os.Stat(trimmedSocket); err != nil { - dm.Logger.Warnf("Error while looking for CRI socket file: %s", err.Error()) + + // Check if cri socket set, if not then auto detect + if cfg.GlobalCfg.CRISocket == "" { + if kl.GetCRISocket("") == "" { + dm.Logger.Warnf("Error while looking for CRI socket file") + } else { + cfg.GlobalCfg.CRISocket = "unix://" + kl.GetCRISocket("") + // update the value of trimmed socket path when the cfg.GlobalCfg.CRISocket is not set + trimmedSocket = trimmedSocketPath() } } - cri, criPath := hostContainerRuntime() - if cri == "docker" { - if cfg.GlobalCfg.CRISocket == "" { - cfg.GlobalCfg.CRISocket = "unix://" + criPath - } + + // monitor containers + if strings.Contains(cfg.GlobalCfg.CRISocket, "docker") { // update already deployed containers dm.GetAlreadyDeployedDockerContainers() - // monitor docker events go dm.MonitorDockerEvents() - - } else if cri == "containerd" { - if cfg.GlobalCfg.CRISocket == "" { - cfg.GlobalCfg.CRISocket = "unix://" + criPath - } + } else if strings.Contains(cfg.GlobalCfg.CRISocket, "containerd") { // monitor containerd events go dm.MonitorContainerdEvents() - - } else if cri == "crio" { - if cfg.GlobalCfg.CRISocket == "" { - cfg.GlobalCfg.CRISocket = "unix://" + criPath - } - // monitor cri-o events + } else if strings.Contains(cfg.GlobalCfg.CRISocket, "crio") { + // monitor crio events go dm.MonitorCrioEvents() - - } else if cri == "unavailable" { - dm.Logger.Warnf("Failed to monitor containers. Unsupported container runtime") + } else { + dm.Logger.Warnf("Failed to monitor containers: %s is not a supported CRI socket.", cfg.GlobalCfg.CRISocket) } + + dm.Logger.Printf("Using %s for monitoring containers", cfg.GlobalCfg.CRISocket) } if dm.K8sEnabled && cfg.GlobalCfg.Policy { // check if the CRI socket set while executing kubearmor exists if cfg.GlobalCfg.CRISocket != "" { - trimmedSocket := strings.TrimPrefix(cfg.GlobalCfg.CRISocket, "unix://") if _, err := os.Stat(trimmedSocket); err != nil { dm.Logger.Warnf("Error while looking for CRI socket file: %s", err.Error()) @@ -542,7 +516,7 @@ func KubeArmor() { return } - dm.Logger.Printf("Using %s for monitoring containers.", cfg.GlobalCfg.CRISocket) + dm.Logger.Printf("Using %s for monitoring containers", cfg.GlobalCfg.CRISocket) } else { // CRI socket not set, we'll have to auto detect dm.Logger.Print("CRI socket not set. Trying to detect.") @@ -642,6 +616,13 @@ func KubeArmor() { policyService := &policy.ServiceServer{} + if !dm.K8sEnabled && cfg.GlobalCfg.Policy { + if _, err := os.Stat(trimmedSocket); err == nil { + policyService.UpdateContainerPolicy = dm.ParseAndUpdateContainerSecurityPolicy + dm.Logger.Print("Started to monitor container security policies on gRPC") + } + } + if !cfg.GlobalCfg.K8sEnv && cfg.GlobalCfg.HostPolicy { policyService.UpdateHostPolicy = dm.ParseAndUpdateHostSecurityPolicy dm.Node.PolicyEnabled = tp.KubeArmorPolicyEnabled @@ -650,13 +631,6 @@ func KubeArmor() { reflection.Register(dm.Logger.LogServer) dm.Logger.Print("Started to monitor host security policies on gRPC") - - } - - if !dm.K8sEnabled && cfg.GlobalCfg.Policy { - policyService.UpdateContainerPolicy = dm.ParseAndUpdateContainerSecurityPolicy - - dm.Logger.Print("Started to monitor container security policies on gRPC") } // serve log feeds diff --git a/KubeArmor/policy/policy.go b/KubeArmor/policy/policy.go index ec8a8a32bd..f221535eb7 100644 --- a/KubeArmor/policy/policy.go +++ b/KubeArmor/policy/policy.go @@ -15,38 +15,38 @@ import ( // ServiceServer provides structure to serve Policy gRPC service type ServiceServer struct { pb.PolicyServiceServer - UpdateHostPolicy func(tp.K8sKubeArmorHostPolicyEvent) UpdateContainerPolicy func(tp.K8sKubeArmorPolicyEvent) + UpdateHostPolicy func(tp.K8sKubeArmorHostPolicyEvent) } -// HostPolicy accepts host policy event on gRPC service and updates host security policies. It responds with 1 if success else 0. -func (p *ServiceServer) HostPolicy(c context.Context, data *pb.Policy) (*pb.Response, error) { - policyEvent := tp.K8sKubeArmorHostPolicyEvent{} +// ContainerPolicy accepts container events on gRPC and update container security policies +func (p *ServiceServer) ContainerPolicy(c context.Context, data *pb.Policy) (*pb.Response, error) { + policyEvent := tp.K8sKubeArmorPolicyEvent{} res := new(pb.Response) err := json.Unmarshal(data.Policy, &policyEvent) if err == nil { - p.UpdateHostPolicy(policyEvent) + p.UpdateContainerPolicy(policyEvent) res.Status = 1 } else { - kg.Warn("Invalid Host Policy Event") + kg.Warn("Invalid Container Policy Event") res.Status = 0 } return res, nil } -// ContainerPolicy accepts container events on gRPC and update container security policies -func (p *ServiceServer) ContainerPolicy(c context.Context, data *pb.Policy) (*pb.Response, error) { - policyEvent := tp.K8sKubeArmorPolicyEvent{} +// HostPolicy accepts host policy event on gRPC service and updates host security policies. It responds with 1 if success else 0. +func (p *ServiceServer) HostPolicy(c context.Context, data *pb.Policy) (*pb.Response, error) { + policyEvent := tp.K8sKubeArmorHostPolicyEvent{} res := new(pb.Response) err := json.Unmarshal(data.Policy, &policyEvent) if err == nil { - p.UpdateContainerPolicy(policyEvent) + p.UpdateHostPolicy(policyEvent) res.Status = 1 } else { - kg.Warn("Invalid Container Policy Event") + kg.Warn("Invalid Host Policy Event") res.Status = 0 } diff --git a/protobuf/policy.pb.go b/protobuf/policy.pb.go index c0305dff5e..2b8bb484c8 100644 --- a/protobuf/policy.pb.go +++ b/protobuf/policy.pb.go @@ -123,13 +123,13 @@ var file_policy_proto_rawDesc = []byte{ 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x20, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x32, 0x74, 0x0a, 0x0d, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a, - 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x0e, 0x2e, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x1a, 0x10, 0x2e, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x1a, 0x10, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x12, 0x0e, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x1a, 0x10, 0x2e, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x61, 0x72, 0x6d, 0x6f, 0x72, 0x2f, 0x4b, 0x75, 0x62, 0x65, 0x41, 0x72, 0x6d, 0x6f, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70, @@ -154,10 +154,10 @@ var file_policy_proto_goTypes = []interface{}{ (*Policy)(nil), // 1: policy.policy } var file_policy_proto_depIdxs = []int32{ - 1, // 0: policy.PolicyService.hostPolicy:input_type -> policy.policy - 1, // 1: policy.PolicyService.containerPolicy:input_type -> policy.policy - 0, // 2: policy.PolicyService.hostPolicy:output_type -> policy.response - 0, // 3: policy.PolicyService.containerPolicy:output_type -> policy.response + 1, // 0: policy.PolicyService.containerPolicy:input_type -> policy.policy + 1, // 1: policy.PolicyService.hostPolicy:input_type -> policy.policy + 0, // 2: policy.PolicyService.containerPolicy:output_type -> policy.response + 0, // 3: policy.PolicyService.hostPolicy:output_type -> policy.response 2, // [2:4] is the sub-list for method output_type 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name diff --git a/protobuf/policy.proto b/protobuf/policy.proto index d04c7c8b2a..e868fcfdaa 100644 --- a/protobuf/policy.proto +++ b/protobuf/policy.proto @@ -13,6 +13,6 @@ message policy { } service PolicyService { - rpc hostPolicy (policy) returns (response); rpc containerPolicy (policy) returns (response); + rpc hostPolicy (policy) returns (response); } diff --git a/protobuf/policy_grpc.pb.go b/protobuf/policy_grpc.pb.go index 6fad8f4cf8..5f8b7a5aec 100644 --- a/protobuf/policy_grpc.pb.go +++ b/protobuf/policy_grpc.pb.go @@ -22,8 +22,8 @@ const _ = grpc.SupportPackageIsVersion7 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type PolicyServiceClient interface { - HostPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) ContainerPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) + HostPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) } type policyServiceClient struct { @@ -34,18 +34,18 @@ func NewPolicyServiceClient(cc grpc.ClientConnInterface) PolicyServiceClient { return &policyServiceClient{cc} } -func (c *policyServiceClient) HostPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) { +func (c *policyServiceClient) ContainerPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) { out := new(Response) - err := c.cc.Invoke(ctx, "/policy.PolicyService/hostPolicy", in, out, opts...) + err := c.cc.Invoke(ctx, "/policy.PolicyService/containerPolicy", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *policyServiceClient) ContainerPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) { +func (c *policyServiceClient) HostPolicy(ctx context.Context, in *Policy, opts ...grpc.CallOption) (*Response, error) { out := new(Response) - err := c.cc.Invoke(ctx, "/policy.PolicyService/containerPolicy", in, out, opts...) + err := c.cc.Invoke(ctx, "/policy.PolicyService/hostPolicy", in, out, opts...) if err != nil { return nil, err } @@ -56,20 +56,20 @@ func (c *policyServiceClient) ContainerPolicy(ctx context.Context, in *Policy, o // All implementations should embed UnimplementedPolicyServiceServer // for forward compatibility type PolicyServiceServer interface { - HostPolicy(context.Context, *Policy) (*Response, error) ContainerPolicy(context.Context, *Policy) (*Response, error) + HostPolicy(context.Context, *Policy) (*Response, error) } // UnimplementedPolicyServiceServer should be embedded to have forward compatible implementations. type UnimplementedPolicyServiceServer struct { } -func (UnimplementedPolicyServiceServer) HostPolicy(context.Context, *Policy) (*Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method HostPolicy not implemented") -} func (UnimplementedPolicyServiceServer) ContainerPolicy(context.Context, *Policy) (*Response, error) { return nil, status.Errorf(codes.Unimplemented, "method ContainerPolicy not implemented") } +func (UnimplementedPolicyServiceServer) HostPolicy(context.Context, *Policy) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method HostPolicy not implemented") +} // UnsafePolicyServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to PolicyServiceServer will @@ -82,38 +82,38 @@ func RegisterPolicyServiceServer(s grpc.ServiceRegistrar, srv PolicyServiceServe s.RegisterService(&PolicyService_ServiceDesc, srv) } -func _PolicyService_HostPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _PolicyService_ContainerPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Policy) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(PolicyServiceServer).HostPolicy(ctx, in) + return srv.(PolicyServiceServer).ContainerPolicy(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/policy.PolicyService/hostPolicy", + FullMethod: "/policy.PolicyService/containerPolicy", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(PolicyServiceServer).HostPolicy(ctx, req.(*Policy)) + return srv.(PolicyServiceServer).ContainerPolicy(ctx, req.(*Policy)) } return interceptor(ctx, in, info, handler) } -func _PolicyService_ContainerPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _PolicyService_HostPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Policy) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(PolicyServiceServer).ContainerPolicy(ctx, in) + return srv.(PolicyServiceServer).HostPolicy(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/policy.PolicyService/containerPolicy", + FullMethod: "/policy.PolicyService/hostPolicy", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(PolicyServiceServer).ContainerPolicy(ctx, req.(*Policy)) + return srv.(PolicyServiceServer).HostPolicy(ctx, req.(*Policy)) } return interceptor(ctx, in, info, handler) } @@ -125,14 +125,14 @@ var PolicyService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "policy.PolicyService", HandlerType: (*PolicyServiceServer)(nil), Methods: []grpc.MethodDesc{ - { - MethodName: "hostPolicy", - Handler: _PolicyService_HostPolicy_Handler, - }, { MethodName: "containerPolicy", Handler: _PolicyService_ContainerPolicy_Handler, }, + { + MethodName: "hostPolicy", + Handler: _PolicyService_HostPolicy_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "policy.proto",