feat: store agent package reports and serve the collect flag

VulnScanningEnabled reads GetLicenseState(...).Feature("vuln_scanning")
and requires an active licence, never switching on tier. ReportPackages
re-checks it server-side: the agent flag is the optimisation, this is
the boundary.
This commit is contained in:
2026-08-06 13:19:39 +01:00
parent a92c3190c2
commit 583f60771c
2 changed files with 182 additions and 1 deletions
+60 -1
View File
@@ -63,7 +63,13 @@ func (s *vantageServer) SyncKeys(ctx context.Context, req *pb.SyncRequest) (*pb.
return nil, status.Errorf(codes.Internal, "failed to build authorized keys: %v", err)
}
return &pb.SyncResponse{PublicKeys: keys}, nil
// Carried on the 30s key poll rather than its own RPC: the agent needs it
// before its hourly package report, and this is the only message it already
// receives that often.
return &pb.SyncResponse{
PublicKeys: keys,
CollectPackages: services.VulnScanningEnabled(srv.InstanceID),
}, nil
}
func (s *vantageServer) UploadGeneratedKey(ctx context.Context, req *pb.UploadKeyRequest) (*pb.UploadKeyResponse, error) {
@@ -104,6 +110,59 @@ func (s *vantageServer) ReportUpdates(ctx context.Context, req *pb.ReportUpdates
return &pb.ReportUpdatesResponse{}, nil
}
// ReportPackages stores a server's installed package set.
//
// It does NOT match against the vulnerability database. Matching happens in
// vulnsched, on the leader: every replica would otherwise need the ~50MB
// database resident, and a database refresh would have N replicas racing to
// rescan the same fleet and sending N digests to the customer.
func (s *vantageServer) ReportPackages(ctx context.Context, req *pb.ReportPackagesRequest) (*pb.ReportPackagesResponse, error) {
srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "invalid agent token")
}
// The agent's collect_packages flag is an optimisation; this is the
// boundary. An agent that ignores the flag still stores nothing.
if !services.VulnScanningEnabled(srv.InstanceID) {
return &pb.ReportPackagesResponse{NeedFull: false}, nil
}
// The offer call: a hash and no packages. Answering NeedFull=false here is
// what saves the ~150KB body on the overwhelming majority of reports.
if len(req.Packages) == 0 {
known, err := services.HasPackageHash(srv.InstanceID, srv.ServerID, req.Hash)
if err != nil {
log.Printf("package hash lookup for %s: %v", srv.ServerID, err)
return nil, status.Errorf(codes.Internal, "package hash lookup failed")
}
return &pb.ReportPackagesResponse{NeedFull: !known}, nil
}
pkgs := make([]models.InstalledPackage, len(req.Packages))
for i, p := range req.Packages {
pkgs[i] = models.InstalledPackage{
Name: p.Name,
Version: p.Version,
Epoch: int(p.Epoch),
Arch: p.Arch,
SourceName: p.SourceName,
}
}
os := models.OSRelease{
Family: req.Os.Family,
VersionID: req.Os.VersionId,
Arch: req.Os.Arch,
}
if err := services.StorePackages(srv.InstanceID, srv.ServerID, os, req.Hash, pkgs); err != nil {
log.Printf("store packages for %s: %v", srv.ServerID, err)
return nil, status.Errorf(codes.Internal, "failed to store packages")
}
return &pb.ReportPackagesResponse{NeedFull: false}, nil
}
func (s *vantageServer) ReportInventory(ctx context.Context, req *pb.InventoryReport) (*pb.InventoryReportResponse, error) {
srv, err := services.ValidateAgentToken(req.ServerId, req.AgentToken)
if err != nil {