Since v0.20.0 tracee includes a new DataSourceService in its gRPC server. This service includes the ability
to write generic data into a specified data source, both through streaming and unary methods.
However, in order to utilize this feature, a speciailized WritableDataSource must be specified in the RPC arguments.
These data sources are currently only available through custom data sources, meaning that no built-in data sources support this feature.
Let us implement an example data source which will give us a configurable threshold for reporting some finding.
Start by adding a file threshold_datasource.go:
packagedatasourcetestimport("encoding/json""github.com/aquasecurity/tracee/types/detect")typethresholdDataSourcestruct{thresholdint}func(ctx*e2eWritable)Get(keyinterface{})(map[string]interface{},error){keyVal,ok:=key.(string)if!ok{returnnil,detect.ErrKeyNotSupported}ifkeyVal!="threshold"{returnnil,detect.ErrKeyNotSupported}returnmap[string]interface{}{"threshold":ctx.threshold,},nil}func(ctx*e2eWritable)Version()uint{return1}func(ctx*e2eWritable)Keys()[]string{return[]string{"string:\"threshold\""}}func(ctx*e2eWritable)Schema()string{schema:=map[string]interface{}{"threshold":"int",}s,_:=json.Marshal(schema)returnstring(s)}func(ctx*e2eWritable)Namespace()string{return"my_namespace"}func(ctx*e2eWritable)ID()string{return"threshold_datasource"}func(ctx*e2eWritable)Write(datamap[interface{}]interface{})error{threshold,ok:=data["threshold"]if!ok{returndetect.ErrFailedToUnmarshal}// Currently we pass the gRPC values directly, so numbers are sent as float64thresholdFloat,ok:=threshold.(float64)if!ok{returndetect.ErrFailedToUnmarshal}ctx.threshold=int(thresholdFloat)returnnil}func(ctx*e2eWritable)Values()[]string{return[]string{"string"}}
Note
Unpacking values from the given data dictionary has a specific quirk about value unwrapping.
Currently only the gRPC API is given for writing to data sources, which uses the struct.proto package for passing generic values.
There is currently no abstraction layer over it, which is why we unpacked the threshold value as float64 in the example, despite wanting
it as an int in the end.
Now we can use this data source just like we would any other in a signature through the following code:
func(sig*mySig)Init(ctxdetect.SignatureContext)error{...thresholdDataSource,ok:=ctx.GetDataSource("my_namespace","threshold_datasource")if!ok{returnerrors.New("threshold data source not registered")}ifthresholdDataSource.Version()>1{returnfmt.Errorf("threshold data source version not supported, please update this signature")}sig.thresholdData=thresholdDataSource}
The following is a short example for a go program which will implement a client for out threshold data source. Note that this is a minimal outline, and you should modify it based on your specific usecase:
packagemainimport("context""flag""fmt""os""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure""google.golang.org/protobuf/types/known/structpb""github.com/aquasecurity/tracee/api/v1beta1")funcprintAndExit(msgstring,args...any){fmt.Printf(msg,args...)os.Exit(1)}funcmain(){traceeAddressPtr:=flag.String("key","","key to set in the data source")thresholdPtr:=flag.Int("value","","key to set in the data source")flag.Parse()traceeAddress:=*traceeAddressPtrthreshold:=*thresholdPtriftraceeAddress==""{printAndExit("empty address given\n")}ifthreshold==0{printAndExit("empty threshold given\n")}ifthreshold<0{printAndExit("negative threshold given\n")}conn,err:=grpc.Dial(traceeAddress,grpc.WithTransportCredentials(insecure.NewCredentials()),)iferr!=nil{printAndExit("failed to dial tracee grpc server: %v\n",err)}client:=v1beta1.NewDataSourceServiceClient(conn)_,err=client.Write(context.Background(),&v1beta1.WriteDataSourceRequest{Id:"my_namespace",Namespace:"threshold_datasource",Key:structpb.NewStringValue("threshold"),Value:structpb.NewNumberValue(float64(threshold)),})iferr!=nil{printAndExit("failed to write to data source: %v\n",err)}}
With all these steps completed, you are ready to impelement and use your own writable data source!