diff --git a/KubeArmor/core/kubeArmor.go b/KubeArmor/core/kubeArmor.go index 18f041915f..e6916ccf7a 100644 --- a/KubeArmor/core/kubeArmor.go +++ b/KubeArmor/core/kubeArmor.go @@ -66,6 +66,10 @@ type KubeArmorDaemon struct { HostSecurityPolicies []tp.HostSecurityPolicy HostSecurityPoliciesLock *sync.RWMutex + //DefaultPosture (namespace -> postures) + DefaultPostures map[string]tp.DefaultPosture + DefaultPosturesLock *sync.Mutex + // container id -> (host) pid ActivePidMap map[string]tp.PidMap ActiveHostPidMap map[string]tp.PidMap @@ -114,6 +118,9 @@ func NewKubeArmorDaemon() *KubeArmorDaemon { dm.HostSecurityPolicies = []tp.HostSecurityPolicy{} dm.HostSecurityPoliciesLock = new(sync.RWMutex) + dm.DefaultPostures = map[string]tp.DefaultPosture{} + dm.DefaultPosturesLock = new(sync.Mutex) + dm.ActivePidMap = map[string]tp.PidMap{} dm.ActiveHostPidMap = map[string]tp.PidMap{} dm.ActivePidMapLock = new(sync.RWMutex) @@ -506,6 +513,10 @@ func KubeArmor() { // watch security policies go dm.WatchSecurityPolicies() dm.Logger.Print("Started to monitor security policies") + + // watch default posture + go dm.WatchDefaultPosture() + dm.Logger.Print("Started to monitor per-namespace default posture") } if dm.K8sEnabled && cfg.GlobalCfg.HostPolicy { diff --git a/KubeArmor/core/kubeUpdate.go b/KubeArmor/core/kubeUpdate.go index ef8b28639a..e170d587da 100644 --- a/KubeArmor/core/kubeUpdate.go +++ b/KubeArmor/core/kubeUpdate.go @@ -4,6 +4,7 @@ package core import ( + "context" "encoding/json" "io" "io/ioutil" @@ -16,6 +17,9 @@ import ( cfg "github.com/kubearmor/KubeArmor/KubeArmor/config" kg "github.com/kubearmor/KubeArmor/KubeArmor/log" tp "github.com/kubearmor/KubeArmor/KubeArmor/types" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" ) // ================= // @@ -218,6 +222,20 @@ func (dm *KubeArmorDaemon) UpdateEndPointWithPod(action string, pod tp.K8sPod) { } dm.ContainersLock.Unlock() + dm.DefaultPosturesLock.Lock() + if val, ok := dm.DefaultPostures[newPoint.NamespaceName]; ok { + newPoint.DefaultPosture = val + } else { + globalDefaultPosture := tp.DefaultPosture{ + FileAction: cfg.GlobalCfg.DefaultFilePosture, + NetworkAction: cfg.GlobalCfg.DefaultNetworkPosture, + CapabilitiesAction: cfg.GlobalCfg.DefaultCapabilitiesPosture, + } + dm.DefaultPostures[newPoint.NamespaceName] = globalDefaultPosture + newPoint.DefaultPosture = globalDefaultPosture + } + dm.DefaultPosturesLock.Unlock() + // update selinux profile names to the endpoint for k, v := range pod.Annotations { if strings.HasPrefix(k, "kubearmor-selinux") { @@ -330,6 +348,20 @@ func (dm *KubeArmorDaemon) UpdateEndPointWithPod(action string, pod tp.K8sPod) { } dm.ContainersLock.Unlock() + dm.DefaultPosturesLock.Lock() + if val, ok := dm.DefaultPostures[newEndPoint.NamespaceName]; ok { + newEndPoint.DefaultPosture = val + } else { + globalDefaultPosture := tp.DefaultPosture{ + FileAction: cfg.GlobalCfg.DefaultFilePosture, + NetworkAction: cfg.GlobalCfg.DefaultNetworkPosture, + CapabilitiesAction: cfg.GlobalCfg.DefaultCapabilitiesPosture, + } + dm.DefaultPostures[newEndPoint.NamespaceName] = globalDefaultPosture + newEndPoint.DefaultPosture = globalDefaultPosture + } + dm.DefaultPosturesLock.Unlock() + // update selinux profile names to the endpoint for k, v := range pod.Annotations { if strings.HasPrefix(k, "kubearmor-selinux") { @@ -1696,3 +1728,84 @@ func (dm *KubeArmorDaemon) restoreKubeArmorHostPolicies() { } } } + +// WatchDefaultPosture Function +func (dm *KubeArmorDaemon) WatchDefaultPosture() { + nsWatcher, err := K8s.K8sClient.CoreV1().Namespaces().Watch(context.Background(), metav1.ListOptions{}) + defer nsWatcher.Stop() + if err == nil { + for resp := range nsWatcher.ResultChan() { + if resp.Type == watch.Modified || resp.Type == watch.Added { + if ns, ok := resp.Object.(*corev1.Namespace); ok { + defaultPosture := tp.DefaultPosture{ + FileAction: validateDefaultPosture("kubearmor-file-posture", ns, cfg.GlobalCfg.DefaultFilePosture), + NetworkAction: validateDefaultPosture("kubearmor-network-posture", ns, cfg.GlobalCfg.DefaultNetworkPosture), + CapabilitiesAction: validateDefaultPosture("kubearmor-capabilities-posture", ns, cfg.GlobalCfg.DefaultCapabilitiesPosture), + } + dm.UpdateDefaultPosture(string(resp.Type), ns.Name, defaultPosture) + + } + } else if resp.Type == watch.Deleted { + if ns, ok := resp.Object.(*corev1.Namespace); ok { + dm.UpdateDefaultPosture(string(resp.Type), ns.Name, tp.DefaultPosture{}) + } + } + } + } +} + +func validateDefaultPosture(key string, ns *corev1.Namespace, defaultPosture string) string { + if posture, ok := ns.Annotations[key]; ok { + if posture == "audit" || posture == "Audit" { + return "audit" + } else if posture == "block" || posture == "Block" { + return "block" + } + // Invalid Annotation Value, Updating the value to global default + ns.Annotations[key] = defaultPosture + updatedNS, err := K8s.K8sClient.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{}) + if err != nil { + kg.Warnf("Error updating invalid default posture annotation for %v", updatedNS) + } + } + return defaultPosture +} + +// UpdateDefaultPosture Function +func (dm *KubeArmorDaemon) UpdateDefaultPosture(action string, namespace string, defaultPosture tp.DefaultPosture) { + dm.EndPointsLock.Lock() + defer dm.EndPointsLock.Unlock() + + dm.DefaultPosturesLock.Lock() + defer dm.DefaultPosturesLock.Unlock() + + if action == "DELETED" { + delete(dm.DefaultPostures, namespace) + } + + dm.DefaultPostures[namespace] = defaultPosture + + for idx, endPoint := range dm.EndPoints { + // update a security policy + if namespace == endPoint.NamespaceName { + if dm.EndPoints[idx].DefaultPosture == defaultPosture { + continue + } + + dm.Logger.UpdateDefaultPosture(action, namespace, defaultPosture) + + dm.EndPoints[idx].DefaultPosture = defaultPosture + dm.Logger.Printf("Updating default posture for %s with %v/%v", endPoint.EndPointName, dm.EndPoints[idx].DefaultPosture, dm.DefaultPostures[namespace]) + + if cfg.GlobalCfg.Policy { + // update security policies + if dm.RuntimeEnforcer != nil { + if dm.EndPoints[idx].PolicyEnabled == tp.KubeArmorPolicyEnabled { + // enforce security policies + dm.RuntimeEnforcer.UpdateSecurityPolicies(dm.EndPoints[idx]) + } + } + } + } + } +} diff --git a/KubeArmor/enforcer/appArmorEnforcer.go b/KubeArmor/enforcer/appArmorEnforcer.go index 00389a7c13..8bf65e2144 100644 --- a/KubeArmor/enforcer/appArmorEnforcer.go +++ b/KubeArmor/enforcer/appArmorEnforcer.go @@ -448,7 +448,7 @@ func (ae *AppArmorEnforcer) UnregisterAppArmorHostProfile() bool { // UpdateAppArmorProfile Function func (ae *AppArmorEnforcer) UpdateAppArmorProfile(endPoint tp.EndPoint, appArmorProfile string, securityPolicies []tp.SecurityPolicy) { - if policyCount, newProfile, ok := ae.GenerateAppArmorProfile(appArmorProfile, securityPolicies); ok { + if policyCount, newProfile, ok := ae.GenerateAppArmorProfile(appArmorProfile, endPoint.DefaultPosture, securityPolicies); ok { newfile, err := os.Create(filepath.Clean("/etc/apparmor.d/" + appArmorProfile)) if err != nil { ae.Logger.Warnf("Unable to open an AppArmor profile (%s, %s)", appArmorProfile, err.Error()) diff --git a/KubeArmor/enforcer/appArmorProfile.go b/KubeArmor/enforcer/appArmorProfile.go index da10fdef8e..e23c5102eb 100644 --- a/KubeArmor/enforcer/appArmorProfile.go +++ b/KubeArmor/enforcer/appArmorProfile.go @@ -12,7 +12,6 @@ import ( "strings" kl "github.com/kubearmor/KubeArmor/KubeArmor/common" - cfg "github.com/kubearmor/KubeArmor/KubeArmor/config" tp "github.com/kubearmor/KubeArmor/KubeArmor/types" ) @@ -813,7 +812,7 @@ func (ae *AppArmorEnforcer) BlockedCapabilitiesMatchCapabilities(cap tp.Capabili // == // // GenerateProfileHead Function -func (ae *AppArmorEnforcer) GenerateProfileHead(processWhiteList, fileWhiteList, networkWhiteList, capabilityWhiteList []string, file, network, capability bool) string { +func (ae *AppArmorEnforcer) GenerateProfileHead(processWhiteList, fileWhiteList, networkWhiteList, capabilityWhiteList []string, file, network, capability bool, defaultPosture tp.DefaultPosture) string { profileHead := " #include \n" profileHead = profileHead + " umount,\n" @@ -822,17 +821,17 @@ func (ae *AppArmorEnforcer) GenerateProfileHead(processWhiteList, fileWhiteList, // AND // -> Atleast one allow policy OR from source allow policy - if cfg.GlobalCfg.DefaultFilePosture == "block" && ((len(processWhiteList) > 0 || len(fileWhiteList) > 0) || !file) { + if defaultPosture.FileAction == "block" && ((len(processWhiteList) > 0 || len(fileWhiteList) > 0) || !file) { } else { profileHead = profileHead + " file,\n" } - if cfg.GlobalCfg.DefaultNetworkPosture == "block" && (len(networkWhiteList) > 0 || !network) { + if defaultPosture.NetworkAction == "block" && (len(networkWhiteList) > 0 || !network) { } else { profileHead = profileHead + " network,\n" } - if cfg.GlobalCfg.DefaultCapabilitiesPosture == "block" && (len(capabilityWhiteList) > 0 || !capability) { + if defaultPosture.CapabilitiesAction == "block" && (len(capabilityWhiteList) > 0 || !capability) { } else { profileHead = profileHead + " capability,\n" } @@ -866,7 +865,7 @@ func (ae *AppArmorEnforcer) GenerateProfileFoot() string { // == // // GenerateProfileBody Function -func (ae *AppArmorEnforcer) GenerateProfileBody(securityPolicies []tp.SecurityPolicy) (int, string) { +func (ae *AppArmorEnforcer) GenerateProfileBody(defaultPosture tp.DefaultPosture, securityPolicies []tp.SecurityPolicy) (int, string) { // preparation count := 0 @@ -1118,17 +1117,17 @@ func (ae *AppArmorEnforcer) GenerateProfileBody(securityPolicies []tp.SecurityPo globalFile = false } - if cfg.GlobalCfg.DefaultFilePosture == "block" && ((len(processWhiteList) > 0 || len(fileWhiteList) > 0) || !file) { + if defaultPosture.FileAction == "block" && ((len(processWhiteList) > 0 || len(fileWhiteList) > 0) || !file) { } else { bodyFromSource = bodyFromSource + " file,\n" } - if cfg.GlobalCfg.DefaultNetworkPosture == "block" && (len(networkWhiteList) > 0 || !network) { + if defaultPosture.NetworkAction == "block" && (len(networkWhiteList) > 0 || !network) { } else { bodyFromSource = bodyFromSource + " network,\n" } - if cfg.GlobalCfg.DefaultCapabilitiesPosture == "block" && (len(capabilityWhiteList) > 0 || !capability) { + if defaultPosture.CapabilitiesAction == "block" && (len(capabilityWhiteList) > 0 || !capability) { } else { bodyFromSource = bodyFromSource + " capability,\n" } @@ -1163,7 +1162,7 @@ func (ae *AppArmorEnforcer) GenerateProfileBody(securityPolicies []tp.SecurityPo // head - profileHead := " ## == PRE START == ##\n" + ae.GenerateProfileHead(processWhiteList, fileWhiteList, networkWhiteList, capabilityWhiteList, globalFile, globalNetwork, globalCapability) + " ## == PRE END == ##\n\n" + profileHead := " ## == PRE START == ##\n" + ae.GenerateProfileHead(processWhiteList, fileWhiteList, networkWhiteList, capabilityWhiteList, globalFile, globalNetwork, globalCapability, defaultPosture) + " ## == PRE END == ##\n\n" // body - together @@ -1193,7 +1192,7 @@ func (ae *AppArmorEnforcer) GenerateProfileBody(securityPolicies []tp.SecurityPo // == // // GenerateAppArmorProfile Function -func (ae *AppArmorEnforcer) GenerateAppArmorProfile(appArmorProfile string, securityPolicies []tp.SecurityPolicy) (int, string, bool) { +func (ae *AppArmorEnforcer) GenerateAppArmorProfile(appArmorProfile string, defaultPosture tp.DefaultPosture, securityPolicies []tp.SecurityPolicy) (int, string, bool) { // check apparmor profile if _, err := os.Stat(filepath.Clean("/etc/apparmor.d/" + appArmorProfile)); os.IsNotExist(err) { @@ -1210,7 +1209,7 @@ func (ae *AppArmorEnforcer) GenerateAppArmorProfile(appArmorProfile string, secu // generate a profile body - count, newProfileBody := ae.GenerateProfileBody(securityPolicies) + count, newProfileBody := ae.GenerateProfileBody(defaultPosture, securityPolicies) newProfile := "## == Managed by KubeArmor == ##\n" + "\n" + diff --git a/KubeArmor/feeder/feeder.go b/KubeArmor/feeder/feeder.go index f9eba445ad..c5187e4577 100644 --- a/KubeArmor/feeder/feeder.go +++ b/KubeArmor/feeder/feeder.go @@ -286,6 +286,10 @@ type Feeder struct { SecurityPolicies map[string]tp.MatchPolicies SecurityPoliciesLock *sync.RWMutex + //DefaultPosture (namespace -> postures) + DefaultPostures map[string]tp.DefaultPosture + DefaultPosturesLock *sync.Mutex + // GKE IsGKE bool } @@ -348,6 +352,9 @@ func NewFeeder(node *tp.Node) *Feeder { fd.SecurityPolicies = map[string]tp.MatchPolicies{} fd.SecurityPoliciesLock = new(sync.RWMutex) + fd.DefaultPostures = map[string]tp.DefaultPosture{} + fd.DefaultPosturesLock = new(sync.Mutex) + // check if GKE if kl.IsInK8sCluster() { if b, err := ioutil.ReadFile(filepath.Clean("/media/root/etc/os-release")); err == nil { diff --git a/KubeArmor/feeder/policyMatcher.go b/KubeArmor/feeder/policyMatcher.go index b95547e502..2a3af81319 100644 --- a/KubeArmor/feeder/policyMatcher.go +++ b/KubeArmor/feeder/policyMatcher.go @@ -206,6 +206,20 @@ func (fd *Feeder) newMatchPolicy(policyEnabled int, policyName, src string, mp i return match } +// UpdateDefaultPosture Function +func (fd *Feeder) UpdateDefaultPosture(action string, namespace string, defaultPosture tp.DefaultPosture) { + + fd.DefaultPosturesLock.Lock() + defer fd.DefaultPosturesLock.Unlock() + + if action == "DELETED" { + delete(fd.DefaultPostures, namespace) + } + + fd.DefaultPostures[namespace] = defaultPosture + +} + // UpdateSecurityPolicies Function func (fd *Feeder) UpdateSecurityPolicies(action string, endPoint tp.EndPoint) { name := endPoint.NamespaceName + "_" + endPoint.EndPointName @@ -1040,23 +1054,36 @@ func (fd *Feeder) UpdateMatchedPolicy(log tp.Log) tp.Log { } } + fd.DefaultPosturesLock.Lock() + defer fd.DefaultPosturesLock.Unlock() + + if _, ok := fd.DefaultPostures[log.NamespaceName]; !ok { + globalDefaultPosture := tp.DefaultPosture{ + FileAction: cfg.GlobalCfg.DefaultFilePosture, + NetworkAction: cfg.GlobalCfg.DefaultNetworkPosture, + CapabilitiesAction: cfg.GlobalCfg.DefaultCapabilitiesPosture, + } + fd.DefaultPostures[log.NamespaceName] = globalDefaultPosture + } + if log.Operation == "Process" { - if setLogFields(cfg.GlobalCfg.DefaultFilePosture, log.ProcessVisibilityEnabled, &log, considerFilePosture) { + if setLogFields(fd.DefaultPostures[log.NamespaceName].FileAction, log.ProcessVisibilityEnabled, &log, considerFilePosture) { return log } } else if log.Operation == "File" { - if setLogFields(cfg.GlobalCfg.DefaultFilePosture, log.FileVisibilityEnabled, &log, considerFilePosture) { + if setLogFields(fd.DefaultPostures[log.NamespaceName].FileAction, log.FileVisibilityEnabled, &log, considerFilePosture) { return log } } else if log.Operation == "Network" { - if setLogFields(cfg.GlobalCfg.DefaultNetworkPosture, log.NetworkVisibilityEnabled, &log, considerNetworkPosture) { + if setLogFields(fd.DefaultPostures[log.NamespaceName].NetworkAction, log.NetworkVisibilityEnabled, &log, considerNetworkPosture) { return log } } else if log.Operation == "Capabilities" { - if setLogFields(cfg.GlobalCfg.DefaultCapabilitiesPosture, log.CapabilitiesVisibilityEnabled, &log, false) { + if setLogFields(fd.DefaultPostures[log.NamespaceName].CapabilitiesAction, log.CapabilitiesVisibilityEnabled, &log, false) { return log } } + } else if log.Type == "MatchedPolicy" { if log.PolicyEnabled == tp.KubeArmorPolicyAudited { if log.Action == "Block" { diff --git a/KubeArmor/types/types.go b/KubeArmor/types/types.go index 2404e1d08a..446d62e95e 100644 --- a/KubeArmor/types/types.go +++ b/KubeArmor/types/types.go @@ -61,6 +61,8 @@ type EndPoint struct { PolicyEnabled int `json:"policyEnabled"` + DefaultPosture DefaultPosture `json:"defaultPosture"` + ProcessVisibilityEnabled bool `json:"processVisibilityEnabled"` FileVisibilityEnabled bool `json:"fileVisibilityEnabled"` NetworkVisibilityEnabled bool `json:"networkVisibilityEnabled"` @@ -471,6 +473,13 @@ type HostSecurityPolicy struct { Spec HostSecuritySpec `json:"spec"` } +// DefaultPosture Structure +type DefaultPosture struct { + FileAction string `json:"file,omitempty"` + NetworkAction string `json:"network,omitempty"` + CapabilitiesAction string `json:"capabilties,omitempty"` +} + // ================== // // == SELinux Rule == // // ================== //