Skip to content

Commit

Permalink
Merge pull request #235 from kubescape/feature/incidents_aggregation
Browse files Browse the repository at this point in the history
Extracting more fields of admission controller for the aggregation
  • Loading branch information
jnathangreeg authored Aug 20, 2024
2 parents e040768 + 36efc7b commit c555d07
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 15 deletions.
67 changes: 67 additions & 0 deletions admission/rules/v1/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package rules

import (
"context"
"fmt"

v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/admission"
"k8s.io/client-go/kubernetes"
)

func GetControllerDetails(event admission.Attributes, clientset kubernetes.Interface) (string, string, string, string, error) {
podName, namespace := event.GetName(), event.GetNamespace()

if podName == "" || namespace == "" {
return "", "", "", "", fmt.Errorf("invalid pod details from admission event")
}

pod, err := GetPodDetails(clientset, podName, namespace)
if err != nil {
return "", "", "", "", fmt.Errorf("failed to get pod details: %v", err)
}

workloadKind, workloadName, workloadNamespace := ExtractPodOwner(pod, clientset)
nodeName := pod.Spec.NodeName

return workloadKind, workloadName, workloadNamespace, nodeName, nil
}

func GetPodDetails(clientset kubernetes.Interface, podName, namespace string) (*v1.Pod, error) {
pod, err := clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get pod: %v", err)
}
return pod, nil
}

func ExtractPodOwner(pod *v1.Pod, clientset kubernetes.Interface) (string, string, string) {
for _, ownerRef := range pod.OwnerReferences {
switch ownerRef.Kind {
case "ReplicaSet":
return resolveReplicaSet(ownerRef, pod.Namespace, clientset)
case "Job":
return resolveJob(ownerRef, pod.Namespace, clientset)
case "StatefulSet", "DaemonSet":
return ownerRef.Kind, ownerRef.Name, pod.Namespace
}
}
return "", "", ""
}

func resolveReplicaSet(ownerRef metav1.OwnerReference, namespace string, clientset kubernetes.Interface) (string, string, string) {
rs, err := clientset.AppsV1().ReplicaSets(namespace).Get(context.TODO(), ownerRef.Name, metav1.GetOptions{})
if err == nil && len(rs.OwnerReferences) > 0 && rs.OwnerReferences[0].Kind == "Deployment" {
return "Deployment", rs.OwnerReferences[0].Name, namespace
}
return "ReplicaSet", ownerRef.Name, namespace
}

func resolveJob(ownerRef metav1.OwnerReference, namespace string, clientset kubernetes.Interface) (string, string, string) {
job, err := clientset.BatchV1().Jobs(namespace).Get(context.TODO(), ownerRef.Name, metav1.GetOptions{})
if err == nil && len(job.OwnerReferences) > 0 && job.OwnerReferences[0].Kind == "CronJob" {
return "CronJob", job.OwnerReferences[0].Name, namespace
}
return "Job", ownerRef.Name, namespace
}
20 changes: 18 additions & 2 deletions admission/rules/v1/r2000_exec_to_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import (
"fmt"
"time"

"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/operator/admission/rules"
"github.com/kubescape/operator/objectcache"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authentication/user"
Expand Down Expand Up @@ -65,6 +69,14 @@ func (rule *R2000ExecToPod) ProcessEvent(event admission.Attributes, access inte
options = event.GetOperationOptions().(*unstructured.Unstructured)
}

client := access.(objectcache.KubernetesCache).GetClientset()

workloadKind, workloadName, workloadNamespace, nodeName, err := GetControllerDetails(event, client)
if err != nil {
logger.L().Error("Failed to get parent workload details", helpers.Error(err))
return nil
}

ruleFailure := GenericRuleFailure{
BaseRuntimeAlert: apitypes.BaseRuntimeAlert{
AlertName: rule.Name(),
Expand Down Expand Up @@ -95,8 +107,12 @@ func (rule *R2000ExecToPod) ProcessEvent(event admission.Attributes, access inte
RuleDescription: fmt.Sprintf("Exec to pod detected on pod %s", event.GetName()),
},
RuntimeAlertK8sDetails: apitypes.RuntimeAlertK8sDetails{
PodName: event.GetName(),
Namespace: event.GetNamespace(),
PodName: event.GetName(),
Namespace: event.GetNamespace(),
WorkloadName: workloadName,
WorkloadNamespace: workloadNamespace,
WorkloadKind: workloadKind,
NodeName: nodeName,
},
RuleID: R2000ID,
}
Expand Down
7 changes: 6 additions & 1 deletion admission/rules/v1/r2000_exec_to_pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rules
import (
"testing"

"github.com/kubescape/operator/objectcache"
"github.com/zeebo/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand Down Expand Up @@ -37,9 +38,13 @@ func TestR2000(t *testing.T) {
)

rule := CreateRuleR2000ExecToPod()
result := rule.ProcessEvent(event, nil)
result := rule.ProcessEvent(event, objectcache.KubernetesCacheMockImpl{})

assert.NotNil(t, result)
assert.Equal(t, "test-workload", result.GetRuntimeAlertK8sDetails().WorkloadName)
assert.Equal(t, "test-namespace", result.GetRuntimeAlertK8sDetails().WorkloadNamespace)
assert.Equal(t, "ReplicaSet", result.GetRuntimeAlertK8sDetails().WorkloadKind)
assert.Equal(t, "test-node", result.GetRuntimeAlertK8sDetails().NodeName)
assert.Equal(t, "Exec to pod detected on pod test-pod", result.GetRuleAlert().RuleDescription)
assert.Equal(t, "test-pod", result.GetRuntimeAlertK8sDetails().PodName)
assert.Equal(t, "test-namespace", result.GetRuntimeAlertK8sDetails().Namespace)
Expand Down
20 changes: 18 additions & 2 deletions admission/rules/v1/r2001_portforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import (
"time"

"github.com/kubescape/operator/admission/rules"
"github.com/kubescape/operator/objectcache"

"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authentication/user"
Expand Down Expand Up @@ -65,6 +69,14 @@ func (rule *R2001PortForward) ProcessEvent(event admission.Attributes, access in
options = event.GetOperationOptions().(*unstructured.Unstructured)
}

client := access.(objectcache.KubernetesCache).GetClientset()

workloadKind, workloadName, workloadNamespace, nodeName, err := GetControllerDetails(event, client)
if err != nil {
logger.L().Error("Failed to get parent workload details", helpers.Error(err))
return nil
}

ruleFailure := GenericRuleFailure{
BaseRuntimeAlert: apitypes.BaseRuntimeAlert{
AlertName: rule.Name(),
Expand Down Expand Up @@ -95,8 +107,12 @@ func (rule *R2001PortForward) ProcessEvent(event admission.Attributes, access in
RuleDescription: fmt.Sprintf("Port forward detected on pod %s", event.GetName()),
},
RuntimeAlertK8sDetails: apitypes.RuntimeAlertK8sDetails{
PodName: event.GetName(),
Namespace: event.GetNamespace(),
PodName: event.GetName(),
Namespace: event.GetNamespace(),
WorkloadName: workloadName,
WorkloadNamespace: workloadNamespace,
WorkloadKind: workloadKind,
NodeName: nodeName,
},
RuleID: R2001ID,
}
Expand Down
7 changes: 6 additions & 1 deletion admission/rules/v1/r2001_portforward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rules
import (
"testing"

"github.com/kubescape/operator/objectcache"
"github.com/zeebo/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand Down Expand Up @@ -37,9 +38,13 @@ func TestR2001(t *testing.T) {
)

rule := CreateRuleR2001PortForward()
result := rule.ProcessEvent(event, nil)
result := rule.ProcessEvent(event, objectcache.KubernetesCacheMockImpl{})

assert.NotNil(t, result)
assert.Equal(t, "test-workload", result.GetRuntimeAlertK8sDetails().WorkloadName)
assert.Equal(t, "test-namespace", result.GetRuntimeAlertK8sDetails().WorkloadNamespace)
assert.Equal(t, "ReplicaSet", result.GetRuntimeAlertK8sDetails().WorkloadKind)
assert.Equal(t, "test-node", result.GetRuntimeAlertK8sDetails().NodeName)
assert.Equal(t, "Port forward detected on pod test-pod", result.GetRuleAlert().RuleDescription)
assert.Equal(t, "test-pod", result.GetRuntimeAlertK8sDetails().PodName)
assert.Equal(t, "test-namespace", result.GetRuntimeAlertK8sDetails().Namespace)
Expand Down
15 changes: 13 additions & 2 deletions admission/webhook/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,37 @@ import (
"github.com/kubescape/k8s-interface/k8sinterface"
exporters "github.com/kubescape/operator/admission/exporter"
"github.com/kubescape/operator/admission/rulebinding"
"github.com/kubescape/operator/objectcache"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/client-go/kubernetes"
)

type AdmissionValidator struct {
kubernetesClient *k8sinterface.KubernetesApi
objectCache objectcache.ObjectCache
exporter *exporters.HTTPExporter
ruleBindingCache rulebinding.RuleBindingCache
}

func NewAdmissionValidator(kubernetesClient *k8sinterface.KubernetesApi, exporter *exporters.HTTPExporter, ruleBindingCache rulebinding.RuleBindingCache) *AdmissionValidator {

func NewAdmissionValidator(kubernetesClient *k8sinterface.KubernetesApi, objectCache objectcache.ObjectCache, exporter *exporters.HTTPExporter, ruleBindingCache rulebinding.RuleBindingCache) *AdmissionValidator {
return &AdmissionValidator{
kubernetesClient: kubernetesClient,
objectCache: objectCache,
exporter: exporter,
ruleBindingCache: ruleBindingCache,
}
}

func (av *AdmissionValidator) GetClientset() kubernetes.Interface {
return av.objectCache.GetKubernetesCache().GetClientset()
}



// We are implementing the Validate method from the ValidationInterface interface.
func (av *AdmissionValidator) Validate(ctx context.Context, attrs admission.Attributes, o admission.ObjectInterfaces) (err error) {
if attrs.GetObject() != nil {
Expand All @@ -45,7 +56,7 @@ func (av *AdmissionValidator) Validate(ctx context.Context, attrs admission.Attr

rules := av.ruleBindingCache.ListRulesForObject(ctx, object)
for _, rule := range rules {
failure := rule.ProcessEvent(attrs, nil)
failure := rule.ProcessEvent(attrs, av.GetClientset())
if failure != nil {
logger.L().Info("Rule failed", helpers.Interface("failure", failure))
av.exporter.SendAdmissionAlert(failure)
Expand Down
6 changes: 2 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/kubescape/go-logger v0.0.22
github.com/kubescape/k8s-interface v0.0.170
github.com/kubescape/kubescape-network-scanner v0.0.15
github.com/kubescape/node-agent v0.2.104-0.20240717091743-bb654cfeab1a
github.com/kubescape/node-agent v0.2.111
github.com/kubescape/opa-utils v0.0.278
github.com/kubescape/storage v0.0.89
github.com/mitchellh/mapstructure v1.5.0
Expand All @@ -35,6 +35,7 @@ require (
go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux v0.44.0
go.opentelemetry.io/otel v1.27.0
go.opentelemetry.io/otel/trace v1.27.0
go.uber.org/zap v1.27.0
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
k8s.io/api v0.30.2
k8s.io/apimachinery v0.30.2
Expand Down Expand Up @@ -247,7 +248,6 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/mod v0.17.0 // indirect
Expand Down Expand Up @@ -278,5 +278,3 @@ require (
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

replace github.com/project-copacetic/copacetic => github.com/anubhav06/copacetic v0.0.0-20230821175613-0a7915a62e10
Loading

0 comments on commit c555d07

Please sign in to comment.