Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compress opens only if path isn't in sbom #175

Merged
merged 1 commit into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func main() {
pool := file.NewPool(filepath.Join(file.DefaultStorageRoot, "metadata.sq3"), 0) // If less than 1, a reasonable default is used.

stopCh := genericapiserver.SetupSignalHandler()
options := server.NewWardleServerOptions(os.Stdout, os.Stderr, osFs, pool)
options := server.NewWardleServerOptions(os.Stdout, os.Stderr, osFs, pool, clusterData.Namespace)
cmd := server.NewCommandStartWardleServer(options, stopCh)

// cleanup task
Expand Down
7 changes: 4 additions & 3 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ func init() {

// ExtraConfig holds custom apiserver config
type ExtraConfig struct {
OsFs afero.Fs
Pool *sqlitemigration.Pool
Namespace string
OsFs afero.Fs
Pool *sqlitemigration.Pool
}

// Config defines the config for the apiserver
Expand Down Expand Up @@ -146,7 +147,7 @@ func (c completedConfig) New() (*WardleServer, error) {
var (
storageImpl = file.NewStorageImpl(c.ExtraConfig.OsFs, file.DefaultStorageRoot, c.ExtraConfig.Pool, Scheme)

applicationProfileStorageImpl = file.NewStorageImplWithCollector(c.ExtraConfig.OsFs, file.DefaultStorageRoot, c.ExtraConfig.Pool, Scheme, file.NewApplicationProfileProcessor())
applicationProfileStorageImpl = file.NewStorageImplWithCollector(c.ExtraConfig.OsFs, file.DefaultStorageRoot, c.ExtraConfig.Pool, Scheme, file.NewApplicationProfileProcessor(c.ExtraConfig.Namespace))
networkNeighborhoodStorageImpl = file.NewStorageImplWithCollector(c.ExtraConfig.OsFs, file.DefaultStorageRoot, c.ExtraConfig.Pool, Scheme, file.NewNetworkNeighborhoodProcessor())
configScanStorageImpl = file.NewConfigurationScanSummaryStorage(storageImpl)
vulnerabilitySummaryStorage = file.NewVulnerabilitySummaryStorage(storageImpl)
Expand Down
17 changes: 10 additions & 7 deletions pkg/cmd/server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,13 @@ type WardleServerOptions struct {

AlternateDNS []string

OsFs afero.Fs
Pool *sqlitemigration.Pool
OsFs afero.Fs
Pool *sqlitemigration.Pool
Namespace string
}

// NewWardleServerOptions returns a new WardleServerOptions
func NewWardleServerOptions(out, errOut io.Writer, osFs afero.Fs, pool *sqlitemigration.Pool) *WardleServerOptions {
func NewWardleServerOptions(out, errOut io.Writer, osFs afero.Fs, pool *sqlitemigration.Pool, namespace string) *WardleServerOptions {
o := &WardleServerOptions{
RecommendedOptions: genericoptions.NewRecommendedOptions(
defaultEtcdPathPrefix,
Expand All @@ -76,8 +77,9 @@ func NewWardleServerOptions(out, errOut io.Writer, osFs afero.Fs, pool *sqlitemi
StdOut: out,
StdErr: errOut,

OsFs: osFs,
Pool: pool,
OsFs: osFs,
Pool: pool,
Namespace: namespace,
}
o.RecommendedOptions.Etcd = nil

Expand Down Expand Up @@ -218,8 +220,9 @@ func (o *WardleServerOptions) Config() (*apiserver.Config, error) {
config := &apiserver.Config{
GenericConfig: serverConfig,
ExtraConfig: apiserver.ExtraConfig{
OsFs: o.OsFs,
Pool: o.Pool,
OsFs: o.OsFs,
Pool: o.Pool,
Namespace: o.Namespace,
},
}
return config, nil
Expand Down
41 changes: 35 additions & 6 deletions pkg/registry/file/applicationprofile_processor.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package file

import (
"context"
"fmt"
"os"
"strconv"

mapset "github.com/deckarep/golang-set/v2"
"github.com/kubescape/go-logger"
loggerhelpers "github.com/kubescape/go-logger/helpers"
"github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers"
"github.com/kubescape/k8s-interface/names"
"github.com/kubescape/storage/pkg/apis/softwarecomposition"
"github.com/kubescape/storage/pkg/registry/file/dynamicpathdetector"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/storage"
)

const (
Expand All @@ -20,23 +24,26 @@ const (
)

type ApplicationProfileProcessor struct {
defaultNamespace string
maxApplicationProfileSize int
storageImpl *StorageImpl
}

func NewApplicationProfileProcessor() *ApplicationProfileProcessor {
func NewApplicationProfileProcessor(defaultNamespace string) *ApplicationProfileProcessor {
maxApplicationProfileSize, err := strconv.Atoi(os.Getenv("MAX_APPLICATION_PROFILE_SIZE"))
if err != nil {
maxApplicationProfileSize = DefaultMaxApplicationProfileSize
}
logger.L().Debug("maxApplicationProfileSize", loggerhelpers.Int("size", maxApplicationProfileSize))
return &ApplicationProfileProcessor{
defaultNamespace: defaultNamespace,
maxApplicationProfileSize: maxApplicationProfileSize,
}
}

var _ Processor = (*ApplicationProfileProcessor)(nil)

func (a ApplicationProfileProcessor) PreSave(object runtime.Object) error {
func (a *ApplicationProfileProcessor) PreSave(object runtime.Object) error {
profile, ok := object.(*softwarecomposition.ApplicationProfile)
if !ok {
return fmt.Errorf("given object is not an ApplicationProfile")
Expand All @@ -48,7 +55,25 @@ func (a ApplicationProfileProcessor) PreSave(object runtime.Object) error {
// Define a function to process a slice of containers
processContainers := func(containers []softwarecomposition.ApplicationProfileContainer) []softwarecomposition.ApplicationProfileContainer {
for i, container := range containers {
containers[i] = deflateApplicationProfileContainer(container)
var sbomSet mapset.Set[string]
// get files from corresponding sbom
sbomName, err := names.ImageInfoToSlug(container.ImageTag, container.ImageID)
if err == nil {
sbom := softwarecomposition.SBOMSyft{}
key := fmt.Sprintf("/spdx.softwarecomposition.kubescape.io/sbomsyft/%s/%s", a.defaultNamespace, sbomName)
if err := a.storageImpl.Get(context.Background(), key, storage.GetOptions{}, &sbom); err == nil {
// fill sbomSet
sbomSet = mapset.NewSet[string]()
for _, f := range sbom.Spec.Syft.Files {
sbomSet.Add(f.Location.RealPath)
}
} else {
logger.L().Debug("failed to get sbom", loggerhelpers.Error(err), loggerhelpers.String("key", key))
}
} else {
logger.L().Debug("failed to get sbom name", loggerhelpers.Error(err), loggerhelpers.String("imageTag", container.ImageTag), loggerhelpers.String("imageID", container.ImageID))
}
containers[i] = deflateApplicationProfileContainer(container, sbomSet)
size += len(containers[i].Execs)
size += len(containers[i].Opens)
}
Expand All @@ -75,10 +100,14 @@ func (a ApplicationProfileProcessor) PreSave(object runtime.Object) error {
return nil
}

func deflateApplicationProfileContainer(container softwarecomposition.ApplicationProfileContainer) softwarecomposition.ApplicationProfileContainer {
opens, err := dynamicpathdetector.AnalyzeOpens(container.Opens, dynamicpathdetector.NewPathAnalyzer(OpenDynamicThreshold))
func (a *ApplicationProfileProcessor) SetStorage(storageImpl *StorageImpl) {
a.storageImpl = storageImpl
}

func deflateApplicationProfileContainer(container softwarecomposition.ApplicationProfileContainer, sbomSet mapset.Set[string]) softwarecomposition.ApplicationProfileContainer {
opens, err := dynamicpathdetector.AnalyzeOpens(container.Opens, dynamicpathdetector.NewPathAnalyzer(OpenDynamicThreshold), sbomSet)
if err != nil {
logger.L().Warning("failed to analyze opens", loggerhelpers.Error(err))
logger.L().Debug("failed to analyze opens", loggerhelpers.Error(err))
opens = DeflateStringer(container.Opens)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/registry/file/applicationprofile_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func TestApplicationProfileProcessor_PreSave(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("MAX_APPLICATION_PROFILE_SIZE", strconv.Itoa(tt.maxApplicationProfileSize))
a := NewApplicationProfileProcessor()
a := NewApplicationProfileProcessor("kubescape")
tt.wantErr(t, a.PreSave(tt.object), fmt.Sprintf("PreSave(%v)", tt.object))
slices.Sort(tt.object.(*softwarecomposition.ApplicationProfile).Spec.Architectures)
assert.Equal(t, tt.want, tt.object)
Expand Down
13 changes: 12 additions & 1 deletion pkg/registry/file/dynamicpathdetector/analyze_opens.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dynamicpathdetector

import (
"errors"
"maps"
"slices"
"strings"
Expand All @@ -9,17 +10,27 @@ import (
types "github.com/kubescape/storage/pkg/apis/softwarecomposition"
)

func AnalyzeOpens(opens []types.OpenCalls, analyzer *PathAnalyzer) ([]types.OpenCalls, error) {
func AnalyzeOpens(opens []types.OpenCalls, analyzer *PathAnalyzer, sbomSet mapset.Set[string]) ([]types.OpenCalls, error) {
if opens == nil {
return nil, nil
}

if sbomSet == nil {
return nil, errors.New("sbomSet is nil")
}

dynamicOpens := make(map[string]types.OpenCalls)
for _, open := range opens {
_, _ = AnalyzeOpen(open.Path, analyzer)
}

for i := range opens {
// sbomSet files have to be always present in the dynamicOpens
if sbomSet.ContainsOne(opens[i].Path) {
dynamicOpens[opens[i].Path] = opens[i]
continue
}

result, err := AnalyzeOpen(opens[i].Path, analyzer)
if err != nil {
continue
Expand Down
32 changes: 30 additions & 2 deletions pkg/registry/file/dynamicpathdetector/tests/analyze_opens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"testing"

mapset "github.com/deckarep/golang-set/v2"
types "github.com/kubescape/storage/pkg/apis/softwarecomposition"
"github.com/kubescape/storage/pkg/registry/file/dynamicpathdetector"
"github.com/stretchr/testify/assert"
Expand All @@ -26,7 +27,34 @@ func TestAnalyzeOpensWithThreshold(t *testing.T) {
},
}

result, err := dynamicpathdetector.AnalyzeOpens(input, analyzer)
result, err := dynamicpathdetector.AnalyzeOpens(input, analyzer, mapset.NewSet[string]())
assert.NoError(t, err)
assert.Equal(t, expected, result)
}

func TestAnalyzeOpensWithThresholdAndExclusion(t *testing.T) {
analyzer := dynamicpathdetector.NewPathAnalyzer(100)

var input []types.OpenCalls
for i := 0; i < 101; i++ {
input = append(input, types.OpenCalls{
Path: fmt.Sprintf("/home/user%d/file.txt", i),
Flags: []string{"READ"},
})
}

expected := []types.OpenCalls{
{
Path: "/home/user42/file.txt",
Flags: []string{"READ"},
},
{
Path: "/home/\u22ef/file.txt",
Flags: []string{"READ"},
},
}

result, err := dynamicpathdetector.AnalyzeOpens(input, analyzer, mapset.NewSet[string]("/home/user42/file.txt"))
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
Expand Down Expand Up @@ -98,7 +126,7 @@ func TestAnalyzeOpensWithFlagMergingAndThreshold(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
analyzer := dynamicpathdetector.NewPathAnalyzer(3)
result, err := dynamicpathdetector.AnalyzeOpens(tt.input, analyzer)
result, err := dynamicpathdetector.AnalyzeOpens(tt.input, analyzer, mapset.NewSet[string]())
assert.NoError(t, err)

assert.Equal(t, tt.expected, result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"
"testing"

mapset "github.com/deckarep/golang-set/v2"
types "github.com/kubescape/storage/pkg/apis/softwarecomposition"
"github.com/kubescape/storage/pkg/registry/file"
"github.com/kubescape/storage/pkg/registry/file/dynamicpathdetector"
Expand Down Expand Up @@ -57,7 +58,7 @@ func BenchmarkAnalyzeOpensVsDeflateStringer(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = file.DeflateStringer(paths)
_, _ = dynamicpathdetector.AnalyzeOpens(paths, analyzer)
_, _ = dynamicpathdetector.AnalyzeOpens(paths, analyzer, mapset.NewSet[string]())
}
b.ReportAllocs()
})
Expand Down
2 changes: 2 additions & 0 deletions pkg/registry/file/networkneighborhood_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ func (a NetworkNeighborhoodProcessor) PreSave(object runtime.Object) error {
return nil
}

func (a NetworkNeighborhoodProcessor) SetStorage(_ *StorageImpl) {}

func deflateNetworkNeighborhoodContainer(container softwarecomposition.NetworkNeighborhoodContainer) softwarecomposition.NetworkNeighborhoodContainer {
return softwarecomposition.NetworkNeighborhoodContainer{
Name: container.Name,
Expand Down
3 changes: 3 additions & 0 deletions pkg/registry/file/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

type Processor interface {
PreSave(object runtime.Object) error
SetStorage(storageImpl *StorageImpl)
}

type DefaultProcessor struct {
Expand All @@ -19,6 +20,8 @@ func (d DefaultProcessor) PreSave(_ runtime.Object) error {
return nil
}

func (d DefaultProcessor) SetStorage(_ *StorageImpl) {}

type Stringer interface {
String() string
}
Expand Down
15 changes: 13 additions & 2 deletions pkg/registry/file/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func NewStorageImpl(appFs afero.Fs, root string, pool *sqlitemigration.Pool, sch
}

func NewStorageImplWithCollector(appFs afero.Fs, root string, conn *sqlitemigration.Pool, scheme *runtime.Scheme, processor Processor) StorageQuerier {
return &StorageImpl{
storageImpl := &StorageImpl{
appFs: appFs,
pool: conn,
locks: utils.NewMapMutex[string](),
Expand All @@ -93,6 +93,8 @@ func NewStorageImplWithCollector(appFs afero.Fs, root string, conn *sqlitemigrat
versioner: storage.APIObjectVersioner{},
watchDispatcher: newWatchDispatcher(),
}
processor.SetStorage(storageImpl)
return storageImpl
}

// Versioner Returns Versioner associated with this interface.
Expand Down Expand Up @@ -554,6 +556,15 @@ func (s *StorageImpl) GuaranteedUpdate(
return err
}

// check object size
annotations := origState.obj.(metav1.Object).GetAnnotations()
if annotations != nil && annotations[helpersv1.StatusMetadataKey] == helpersv1.TooLarge {
logger.L().Ctx(ctx).Debug("GuaranteedUpdate - already too large object, skipping update", helpers.String("key", key))
// no change, return the original object
v.Set(reflect.ValueOf(origState.obj).Elem())
return nil
}

for {
// run preconditions
if err := preconditions.Check(key, origState.obj); err != nil {
Expand Down Expand Up @@ -621,7 +632,7 @@ func (s *StorageImpl) GuaranteedUpdate(
annotations := metadata.GetAnnotations()
annotations[helpersv1.StatusMetadataKey] = helpersv1.TooLarge
metadata.SetAnnotations(annotations)
logger.L().Ctx(ctx).Warning("GuaranteedUpdate - too large object, skipping update", helpers.String("key", key))
logger.L().Ctx(ctx).Debug("GuaranteedUpdate - too large object, skipping update", helpers.String("key", key))
} else {
return fmt.Errorf("processor.PreSave: %w", err)
}
Expand Down
Loading