Skip to content
Closed
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
)

require (
github.com/cilium/ebpf v0.19.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/cilium/ebpf v0.19.0 h1:Ro/rE64RmFBeA9FGjcTc+KmCeY6jXmryu6FfnzPRIao=
github.com/cilium/ebpf v0.19.0/go.mod h1:fLCgMo3l8tZmAdM3B2XqdFzXBpwkcSTroaVqN08OWVY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
Expand Down
60 changes: 60 additions & 0 deletions handlers/consumer/consumer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package consumer

import (
"net/http"
"time"
"vm-server/jobs"
"vm-server/models"
services "vm-server/services/consumer"

"github.com/google/uuid"
"github.com/labstack/echo/v4"
)

// Handler holds the service and scanner for the scan handlers.
type Handler struct {
Service *services.Service
Scanner *jobs.ConsumerScanner
}

// NewHandler creates a new scan handler.
func NewHandler(s *services.Service, sc *jobs.ConsumerScanner) *Handler {
return &Handler{Service: s, Scanner: sc}
}

// ScanHandler handles scan related requests.
func (h *Handler) ConsumerHandler(c echo.Context) error {
var req models.ConsumerRequest
if err := c.Bind(&req); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request payload"})
}

jobID := uuid.New().String()
newJob := &models.ConsumerJob{
JobID: jobID,
Status: "pending",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

if err := h.Service.CreateConsumerJob(newJob); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to create consumer job"})
}

// Run the scan in the background
// TODO - Make this a channel so we can perform operations on failed scans as well.
go h.Scanner.PerformScan(newJob, &req)

return c.JSON(http.StatusAccepted, map[string]string{"jobId": jobID})
}

// ScanByIDHandler handles retrieving a scan by its ID.
func (h *Handler) ConsumerByIDHandler(c echo.Context) error {
id := c.Param("id")
consumerJob, err := h.Service.GetConsumerJob(id)
if err != nil {
return c.JSON(http.StatusNotFound, map[string]string{"error": "Consumer job not found"})
}

return c.JSON(http.StatusOK, consumerJob)
}
18 changes: 18 additions & 0 deletions jobs/consumer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package jobs

import (
"vm-server/models"
consumer "vm-server/services/consumer"
)

// ConsumerScanner defines the structure for our scanning job runner.
type ConsumerScanner struct {
Service *consumer.Service
}

func (s *ConsumerScanner) PerformScan(job *models.ConsumerJob, req *models.ConsumerRequest) {
// TODO: This is where the trigger to add new config for the ebpf probe
// / start the ebpf probe
// / get info from the already running ebpf probe
// should happen
}
61 changes: 61 additions & 0 deletions models/consumer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package models

import (
"database/sql/driver"
"encoding/json"
"errors"
"time"

"gorm.io/gorm"
)

type ConsumerEntry struct {
gorm.Model
EntryID string `json:"entryId" gorm:"unique;index"`
Hostname string `json:"hostname"`
Executable string `json:"executable"`
User string `json:"user"`
PID string `json:"pid"`
StartTimestamp string `json:"startTimestamp"`
ConsumerJobID uint `json:"consumerJobId"` // Foreign key for ConsumerJob
}

type ConsumerJob struct {
gorm.Model
JobID string `json:"jobId" gorm:"unique;index"`
Status string `json:"status"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
FinishedAt time.Time `json:"finishedAt"`
Match ConsumerSliceMatch `json:"match" gorm:"type:text"` // One-to-many relationship
}

type ConsumerSliceMatch []ConsumerMatchResumed
type ConsumerMatchResumed struct {
Hostname string `json:"hostname"`
Executable string `json:"executable"`
User string `json:"user"`
PID string `json:"pid"`
StartTimestamp string `json:"startTimestamp"`
}

// Value implements the driver.Valuer interface, converting the slice to a JSON string.
func (s ConsumerSliceMatch) Value() (driver.Value, error) {
if len(s) == 0 {
return nil, nil
}
return json.Marshal(s)
}

// Scan implements the sql.Scanner interface, converting the JSON string from the database to a slice.
func (s *ConsumerSliceMatch) Scan(value interface{}) error {
if value == nil {
*s = nil
return nil
}
bytes, ok := value.([]byte)
if !ok {
return errors.New("type assertion to []byte failed")
}
return json.Unmarshal(bytes, s)
}
18 changes: 18 additions & 0 deletions models/consumer_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package models

type ConsumerRequest struct {
Location ConsumerLocation `json:"location"`
Paths []string `json:"paths"`
}

type ConsumerLocation struct {
Kind string `json:"kind"`
Name string `json:"name"`
APIVersion string `json:"apiVersion"`
RemoteRef RemoteRef `json:"remoteRef"`
}

type ConsumerRemoteRef struct {
Key string `json:"key"`
Property string `json:"property"`
}
24 changes: 24 additions & 0 deletions models/consumer_response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package models

import "time"

type ConsumerResponse struct {
ID string `json:"id"`
}

type ConsumerIdResponse struct {
ID string `json:"id"`
Status ScanStatus `json:"status"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
FinishedAt time.Time `json:"finishedAt"`
Consumers []Consumer `json:"consumers"`
}

type Consumer struct {
Hostname string `json:"hostname"`
Executable string `json:"executable"`
User string `json:"user"`
PID string `json:"pid"`
StartTimestamp string `json:"startTimestamp"`
}
57 changes: 57 additions & 0 deletions services/consumer/consumers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package consumer

import (
"vm-server/models"
"vm-server/store/schema"
)

// Service is the business logic layer for consumers.
type Service struct {
Store schema.Store
}

// NewService creates a new consumer service.
func NewService(s schema.Store) *Service {
return &Service{Store: s}
}

// CreateConsumerJob creates a new consumer job.
func (s *Service) CreateConsumerJob(consumerJob *models.ConsumerJob) error {
// Business logic for creating a job can go here.
return s.Store.CreateConsumerJob(consumerJob)
}

// GetConsumerJob retrieves a consumer job by its ID.
func (s *Service) GetConsumerJob(id string) (*models.ConsumerJob, error) {
return s.Store.GetConsumerJob(id)
}

// CreateConsumerEntry creates a new consumer entry.
func (s *Service) CreateConsumerEntry(consumerEntry *models.ConsumerEntry) error {
// Business logic for creating an entry can go here.
return s.Store.CreateConsumerEntry(consumerEntry)
}

// GetConsumerEntry retrieves a consumer entry by its EntryID.
func (s *Service) GetConsumerEntry(entryID string) (*models.ConsumerEntry, error) {
return s.Store.GetConsumerEntry(entryID)
}

// UpdateConsumerEntry updates an existing consumer entry.
func (s *Service) UpdateConsumerEntry(consumerEntry *models.ConsumerEntry) error {
return s.Store.UpdateConsumerEntry(consumerEntry)
}

// UpdateConsumerJob updates an existing consumer job.
func (s *Service) UpdateConsumerJob(consumerJob *models.ConsumerJob) error {
// Business logic for updating a job can go here.
return s.Store.UpdateConsumerJob(consumerJob)
}

func (s *Service) DeleteConsumerEntry(consumerEntry *models.ConsumerEntry) error {
return s.Store.DeleteConsumerEntry(consumerEntry)
}

func (s *Service) ListConsumerEntries() ([]models.ConsumerEntry, error) {
return s.Store.ListConsumerEntries()
}
6 changes: 0 additions & 6 deletions services/secrets/secrets.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package secrets

import (
"fmt"
"os"
"vm-server/models"
"vm-server/services/scan"
Expand All @@ -25,15 +24,10 @@ func (s *Service) UpdateVersion(newValue []byte, entry *models.ScanEntry) error
return err
}
newFileLength := len(file) - (entry.EndLine - entry.StartLine + 1) + len(newValue)
fmt.Printf("old file length: %v\nold Entry Len: %v\n, new Entry Len: %v\n, new file len: %v\n", len(file), entry.EndLine-entry.StartLine+1, len(newValue), newFileLength)
modFile := make([]byte, newFileLength)
fmt.Printf("modifying %v with %v", string(file[entry.StartLine:entry.EndLine]), string(newValue))
copy(modFile[:entry.StartLine], file[:entry.StartLine])
fmt.Printf("New modFile before start: %v\n", string(modFile[:entry.StartLine]))
copy(modFile[entry.StartLine:entry.StartLine+len(newValue)], newValue)
fmt.Printf("New modFile up until beginning: %v\n", string(modFile[:entry.StartLine+len(newValue)]))
copy(modFile[entry.StartLine+len(newValue):], file[entry.EndLine+1:])
fmt.Printf("New modFile after end: %v\n", string(modFile))

err = os.WriteFile(entry.FilePath, modFile, 0644)
if err != nil {
Expand Down
70 changes: 69 additions & 1 deletion store/gorm/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func NewStore() (schema.Store, error) {
log.Println("Database connection established.")

// Auto-migrate the schema for ScanJob and ScanEntry models.
err = db.AutoMigrate(&models.ScanJob{}, &models.ScanEntry{})
err = db.AutoMigrate(&models.ScanJob{}, &models.ScanEntry{}, &models.ConsumerJob{}, &models.ConsumerEntry{})
if err != nil {
// Attempt to close the database connection if migration fails.
sqlDB, _ := db.DB()
Expand Down Expand Up @@ -113,3 +113,71 @@ func (s *StoreGorm) ListScanEntries() ([]models.ScanEntry, error) {
result := s.DB.Find(&scanEntries)
return scanEntries, result.Error
}

// Consumer Job
func (s *StoreGorm) CreateConsumerJob(consumerJob *models.ConsumerJob) error {
result := s.DB.Create(consumerJob)
return result.Error
}

func (s *StoreGorm) GetConsumerJob(id string) (*models.ConsumerJob, error) {
var consumerJob models.ConsumerJob
result := s.DB.First(&consumerJob, "job_id = ?", id)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &models.NotFoundErr{}
}
return nil, result.Error
}
return &consumerJob, nil
}

func (s *StoreGorm) UpdateConsumerJob(consumerJob *models.ConsumerJob) error {
result := s.DB.Save(consumerJob)
return result.Error
}

func (s *StoreGorm) DeleteConsumerJob(consumerJob *models.ConsumerJob) error {
result := s.DB.Delete(consumerJob)
return result.Error
}

func (s *StoreGorm) ListConsumerJobs() ([]models.ConsumerJob, error) {
var consumerJobs []models.ConsumerJob
result := s.DB.Find(&consumerJobs)
return consumerJobs, result.Error
}

// Consumer Entry
func (s *StoreGorm) CreateConsumerEntry(consumerEntry *models.ConsumerEntry) error {
result := s.DB.Create(consumerEntry)
return result.Error
}

func (s *StoreGorm) GetConsumerEntry(entryID string) (*models.ConsumerEntry, error) {
var consumerEntry models.ConsumerEntry
result := s.DB.First(&consumerEntry, "entry_id = ?", entryID)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, &models.NotFoundErr{}
}
return nil, result.Error
}
return &consumerEntry, nil
}

func (s *StoreGorm) UpdateConsumerEntry(consumerEntry *models.ConsumerEntry) error {
result := s.DB.Save(consumerEntry)
return result.Error
}

func (s *StoreGorm) DeleteConsumerEntry(consumerEntry *models.ConsumerEntry) error {
result := s.DB.Delete(consumerEntry)
return result.Error
}

func (s *StoreGorm) ListConsumerEntries() ([]models.ConsumerEntry, error) {
var consumerEntries []models.ConsumerEntry
result := s.DB.Find(&consumerEntries)
return consumerEntries, result.Error
}
Loading
Loading