Skip to content

Commit

Permalink
Field selectors support in RuleMatchObjectExtend and scan object supp…
Browse files Browse the repository at this point in the history
…ort in httpserver API (#127)

* added fields selected to RuleMatchObjects

Signed-off-by: Amir Malka <[email protected]>

* omitempty fieldselector

Signed-off-by: Amir Malka <[email protected]>

* scan workload support in httpserver api

Signed-off-by: Amir Malka <[email protected]>

* CR changes

Signed-off-by: Amir Malka <[email protected]>

* increase coverage

Signed-off-by: Amir Malka <[email protected]>

---------

Signed-off-by: Amir Malka <[email protected]>
  • Loading branch information
amirmalka authored Jul 30, 2023
1 parent 823bc6f commit d4eafdd
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 7 deletions.
12 changes: 8 additions & 4 deletions httpserver/meta/v1/datastructure.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package v1

import v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
import (
v1 "github.com/kubescape/opa-utils/httpserver/apis/v1"
"github.com/kubescape/opa-utils/objectsenvelopes"
)

// A request to trigger a Kubescape scan
type PostScanRequest struct {
Expand Down Expand Up @@ -70,9 +73,10 @@ type PostScanRequest struct {
//
// Example: false
UseCachedArtifacts *bool `json:"useCachedArtifacts,omitempty"`
// UseExceptions string // Load file with exceptions configuration
// ControlsInputs string // Load file with inputs for controls
// VerboseMode bool // Display all of the input resources and not only failed resources
// Scan a specific K8S object
//
// Example: {"apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "nginx", "namespace": "my-namespace"} }
ScanObject *objectsenvelopes.ScanObject `json:"scanObject,omitempty"`
}

// A Scan Response object
Expand Down
156 changes: 156 additions & 0 deletions objectsenvelopes/scanobject.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package objectsenvelopes

import (
"encoding/json"
"fmt"

"github.com/kubescape/k8s-interface/k8sinterface"
"github.com/kubescape/k8s-interface/workloadinterface"
)

var _ workloadinterface.IMetadata = (*ScanObject)(nil)

type ScanObjectMetadata struct {
Name string `json:"name"`
Namespace string `json:"namespace,omitempty"`
}

// A ScanObject represents a K8S object to be scanned
type ScanObject struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata ScanObjectMetadata `json:"metadata"`
}

func IsTypeScanObject(object map[string]interface{}) bool {
if object == nil {
return false
}

if _, ok := object["apiVersion"]; !ok {
return false
}
if _, ok := object["kind"]; !ok {
return false
}

if _, ok := object["metadata"]; !ok {
return false
}

metadata, ok := object["metadata"].(map[string]interface{})
if !ok {
return false
}

if _, ok = metadata["name"]; !ok {
return false
}

return true
}

// NewScanObject construct a ScanObject from map[string]interface{}. If the map does not match the object, will return nil
func NewScanObject(object map[string]interface{}) *ScanObject {
if !IsTypeScanObject(object) {
return nil
}

scanObject := &ScanObject{}
if b := workloadinterface.MapToBytes(object); b != nil {
if err := json.Unmarshal(b, scanObject); err != nil {
return nil
}
} else {
return nil
}
return scanObject
}

func (scanObject *ScanObject) GetNamespace() string {
return scanObject.Metadata.GetNamespace()
}

func (scanObjectMetadata *ScanObjectMetadata) GetName() string {
return scanObjectMetadata.Name
}

func (scanObjectMetadata *ScanObjectMetadata) GetNamespace() string {
return scanObjectMetadata.Namespace
}

func (scanObjectMetadata *ScanObjectMetadata) SetName(name string) {
scanObjectMetadata.Name = name
}

func (scanObjectMetadata *ScanObjectMetadata) SetNamespace(namespace string) {
scanObjectMetadata.Namespace = namespace
}

func (scanObject *ScanObject) GetName() string {
return scanObject.Metadata.GetName()
}

func (scanObject *ScanObject) GetKind() string {
return scanObject.Kind
}

func (scanObject *ScanObject) GetApiVersion() string {
return scanObject.ApiVersion
}

func (scanObject *ScanObject) GetWorkload() map[string]interface{} {
return scanObject.GetObject()
}

func (scanObject *ScanObject) GetObject() map[string]interface{} {
m := map[string]interface{}{}
b, err := json.Marshal(*scanObject)
if err != nil {
return m
}
return workloadinterface.BytesToMap(b)
}

func (scanObject *ScanObject) GetID() string {
return fmt.Sprintf("%s/%s/%s/%s", k8sinterface.JoinGroupVersion(k8sinterface.SplitApiVersion(scanObject.GetApiVersion())), scanObject.GetNamespace(), scanObject.GetKind(), scanObject.GetName())
}

func (scanObject *ScanObject) GetObjectType() workloadinterface.ObjectType {
return GetObjectType(scanObject.GetObject())
}

func (scanObject *ScanObject) SetNamespace(namespace string) {
scanObject.Metadata.SetNamespace(namespace)
}

func (scanObject *ScanObject) SetName(name string) {
scanObject.Metadata.SetName(name)
}

func (scanObject *ScanObject) SetKind(kind string) {
scanObject.Kind = kind
}

func (scanObject *ScanObject) SetWorkload(object map[string]interface{}) {
scanObject.SetObject(object)
}

func (scanObject *ScanObject) SetObject(object map[string]interface{}) {
if !IsTypeScanObject(object) {
return
}
if b := workloadinterface.MapToBytes(object); len(b) > 0 {
obj := &ScanObject{}
if err := json.Unmarshal(b, obj); err == nil {
scanObject.SetApiVersion(obj.GetApiVersion())
scanObject.SetKind(obj.GetKind())
scanObject.SetName(obj.GetName())
scanObject.SetNamespace(obj.GetNamespace())
}
}
}

func (scanObject *ScanObject) SetApiVersion(apiVersion string) {
scanObject.ApiVersion = apiVersion
}
134 changes: 134 additions & 0 deletions objectsenvelopes/scanobject_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package objectsenvelopes

import (
"encoding/json"
"testing"

"github.com/kubescape/k8s-interface/workloadinterface"
"github.com/stretchr/testify/assert"
)

func TestNewScanObject(t *testing.T) {
// Test nil input
assert.Nil(t, NewScanObject(nil))

// Test valid input
object := map[string]interface{}{
"kind": "Pod",
"apiVersion": "v1",
"metadata": map[string]interface{}{
"name": "test-pod",
"namespace": "test-namespace",
},
}
scanObj := NewScanObject(object)
assert.NotNil(t, scanObj)
assert.Equal(t, "Pod", scanObj.GetKind())
assert.Equal(t, "v1", scanObj.GetApiVersion())
assert.Equal(t, "test-pod", scanObj.GetName())
assert.Equal(t, "test-namespace", scanObj.GetNamespace())
assert.Equal(t, workloadinterface.TypeWorkloadObject, scanObj.GetObjectType())

// Test unsupported input
unsupportedObject := map[string]interface{}{
"unknown": "unknown",
}
assert.Nil(t, NewScanObject(unsupportedObject))
}

func TestScanObjectMarshal(t *testing.T) {
scanObj := ScanObject{
Kind: "Pod",
ApiVersion: "v1",
Metadata: ScanObjectMetadata{
Name: "test-pod",
Namespace: "test-namespace",
},
}

// test json marshal
jsonEnc, err := json.Marshal(scanObj)
expectedJsonStr := `{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-pod","namespace":"test-namespace"}}`
assert.NoError(t, err)
assert.Equal(t, expectedJsonStr, string(jsonEnc))
}

func TestScanObjectUnmarshal(t *testing.T) {
var scanObj ScanObject
jsonStr := `{"apiVersion":"v1","kind":"Node","metadata":{"name":"node-1"},"field":"value"}`
err := json.Unmarshal([]byte(jsonStr), &scanObj)
assert.NoError(t, err)
assert.Equal(t, "Node", scanObj.GetKind())
assert.Equal(t, "node-1", scanObj.GetName())
assert.Equal(t, "", scanObj.GetNamespace())
}

func TestScanObjectSetters(t *testing.T) {
scanObj := ScanObject{}

scanObj.SetKind("Node")
assert.Equal(t, "Node", scanObj.Kind)

scanObj.SetApiVersion("v2")
assert.Equal(t, "v2", scanObj.ApiVersion)

scanObj.SetName("node-1")
assert.Equal(t, "node-1", scanObj.Metadata.Name)

scanObj.SetNamespace("namespace")
assert.Equal(t, "namespace", scanObj.Metadata.Namespace)

// test invalid objects - should remain unchanged
scanObj.SetObject(map[string]interface{}{
"invalid": "object",
})
scanObj.SetObject(map[string]interface{}{
"apiVersion": "v3",
})
scanObj.SetObject(map[string]interface{}{
"apiVersion": "v3",
"kind": "Secret",
})
scanObj.SetObject(map[string]interface{}{
"apiVersion": "v3",
"kind": "Secret",
"metadata": "invalid_type",
})
scanObj.SetObject(map[string]interface{}{
"apiVersion": "v3",
"kind": "Secret",
"metadata": map[string]interface{}{
"namespace": "ns-1",
},
})
assert.Equal(t, "Node", scanObj.Kind)

scanObj.SetObject(map[string]interface{}{
"kind": "Pod",
"apiVersion": "v1",
"metadata": map[string]interface{}{
"name": "test-pod",
},
})
assert.Equal(t, "Pod", scanObj.Kind)
assert.Equal(t, "v1", scanObj.ApiVersion)
assert.Equal(t, "test-pod", scanObj.Metadata.Name)
}

func TestScanObjectGetters(t *testing.T) {
scanObj := ScanObject{
Kind: "Pod",
ApiVersion: "v1",
Metadata: ScanObjectMetadata{
Name: "test-pod",
Namespace: "test-namespace",
},
}

assert.Equal(t, "Pod", scanObj.GetKind())
assert.Equal(t, "v1", scanObj.GetApiVersion())
assert.Equal(t, "test-pod", scanObj.GetName())
assert.Equal(t, "test-namespace", scanObj.GetNamespace())
assert.Equal(t, workloadinterface.TypeWorkloadObject, scanObj.GetObjectType())
assert.Equal(t, "/v1/test-namespace/Pod/test-pod", scanObj.GetID())
}
7 changes: 4 additions & 3 deletions reporthandling/datastructures.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ const (

// RuleMatchObjects defines which objects this rule applied on
type RuleMatchObjects struct {
APIGroups []string `json:"apiGroups" bson:"apiGroups"` // apps
APIVersions []string `json:"apiVersions" bson:"apiVersions"` // v1/ v1beta1 / *
Resources []string `json:"resources" bson:"resources"` // dep.., pods,
APIGroups []string `json:"apiGroups" bson:"apiGroups"` // apps
APIVersions []string `json:"apiVersions" bson:"apiVersions"` // v1/ v1beta1 / *
Resources []string `json:"resources" bson:"resources"` // dep.., pods,
FieldSelector []string `json:"fieldSelector,omitempty" bson:"fieldSelector,omitempty"` // fields selector for example metadata.name==nginx,metadata.namespace==ns1
}

type RuleDependency struct {
Expand Down

0 comments on commit d4eafdd

Please sign in to comment.