Skip to content

Commit

Permalink
handle per namespace default posture implementation
Browse files Browse the repository at this point in the history
 - watch for annotation changes per namespace
 - validate annotation key/value, update value with global posture if invalid
 - maintain ns based defaultPosture map
 - update endpoint with defaultPosture details based on map/global config
 - consider endpoint defaultPosture instead of global in enforcement
 - consider defaultPosture from ns map instead of global in feeder

Signed-off-by: daemon1024 <[email protected]>
  • Loading branch information
daemon1024 committed Mar 28, 2022
1 parent 0971f72 commit 160a89a
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 17 deletions.
11 changes: 11 additions & 0 deletions KubeArmor/core/kubeArmor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
113 changes: 113 additions & 0 deletions KubeArmor/core/kubeUpdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package core

import (
"context"
"encoding/json"
"io"
"io/ioutil"
Expand All @@ -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"
)

// ================= //
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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])
}
}
}
}
}
}
2 changes: 1 addition & 1 deletion KubeArmor/enforcer/appArmorEnforcer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
23 changes: 11 additions & 12 deletions KubeArmor/enforcer/appArmorProfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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 <abstractions/base>\n"
profileHead = profileHead + " umount,\n"

Expand All @@ -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"
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
}
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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) {
Expand All @@ -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" +
Expand Down
7 changes: 7 additions & 0 deletions KubeArmor/feeder/feeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down
35 changes: 31 additions & 4 deletions KubeArmor/feeder/policyMatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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" {
Expand Down
9 changes: 9 additions & 0 deletions KubeArmor/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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 == //
// ================== //
Expand Down

0 comments on commit 160a89a

Please sign in to comment.