Persist and surface endpoint installed-software (SBOM)
The endpoint agent already reports an installed-software list (SBOM), but ingress dropped it: the collector schema had no entry for it and there was nowhere to store it, so operators could not see what is installed on a PC. Persist it as a per-asset snapshot in a new endpointapplications table, replaced on every report (current inventory is what operators ask for, not install history). Add applications to the collector schema and KNOWN_PAYLOAD_KEYS so ingress stops discarding it, a read route for the raw collection, and a collapsed-by-default Installed software panel to surface it. appid is nullable groundwork for a future Managed Applications link; no matcher is wired yet.
This commit is contained in:
@@ -246,3 +246,80 @@ class EndpointEvent(db.Model):
|
||||
'eventmessage': self.eventmessage,
|
||||
'recordedat': _utciso(self.recordedat),
|
||||
}
|
||||
|
||||
|
||||
class EndpointApplication(db.Model):
|
||||
"""One installed-software (SBOM) entry reported for an endpoint.
|
||||
|
||||
The agent enumerates both Windows Uninstall registry hives and emits a
|
||||
per-item {displayname, displayversion, publisher}, de-duped on
|
||||
name+version. The server stores that as a per-asset SNAPSHOT: on each
|
||||
report the existing rows for the asset are replaced with the reported set,
|
||||
mirroring how the services/ports inventories are kept as the newest list
|
||||
rather than an append-only history. History of installs is not what
|
||||
operators ask for; the current inventory is.
|
||||
|
||||
appid is groundwork for a future link to the core applications catalog
|
||||
(the "Managed Applications" concept): a nullable, unenforced-by-match FK
|
||||
that a later matcher can populate to associate a discovered install with a
|
||||
managed Application row. No matching logic exists yet - the column and the
|
||||
relationship are laid down here so the association can be added without a
|
||||
second migration.
|
||||
"""
|
||||
|
||||
__tablename__ = 'endpointapplications'
|
||||
|
||||
endpointapplicationid = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
# The asset this install belongs to. Not unique: many applications per
|
||||
# endpoint. CASCADE so removing the asset clears its SBOM snapshot.
|
||||
assetid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('assets.assetid', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
asset = db.relationship('Asset', lazy='joined', viewonly=True)
|
||||
|
||||
# Wire fields, stored under the same lowercase/no-underscore names the agent
|
||||
# emits. displayname is always present (the agent skips entries without it);
|
||||
# displayversion and publisher default to '' on the agent side.
|
||||
displayname = db.Column(db.String(255), nullable=False, index=True)
|
||||
displayversion = db.Column(db.String(128), nullable=True)
|
||||
publisher = db.Column(db.String(255), nullable=True)
|
||||
|
||||
# Groundwork link to the core applications catalog ("Managed Applications").
|
||||
# Nullable and unpopulated for now: no discovery-to-catalog matcher is wired
|
||||
# yet. SET NULL so retiring a managed Application does not delete discovered
|
||||
# SBOM rows - the install is still on the machine, it is just no longer
|
||||
# managed.
|
||||
appid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('applications.appid', ondelete='SET NULL'),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
|
||||
application = db.relationship('Application', lazy='select', viewonly=True)
|
||||
|
||||
# When this snapshot row was written (the report that produced it).
|
||||
recordedat = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
db.Index('idx_endpointapplications_assetname', 'assetid', 'displayname'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<EndpointApplication {self.displayname!r} asset={self.assetid}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'endpointapplicationid': self.endpointapplicationid,
|
||||
'assetid': self.assetid,
|
||||
'displayname': self.displayname,
|
||||
'displayversion': self.displayversion,
|
||||
'publisher': self.publisher,
|
||||
'appid': self.appid,
|
||||
'recordedat': _utciso(self.recordedat),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user