mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
235 lines
9 KiB
Go
235 lines
9 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server"
|
|
"github.com/fleetdm/fleet/v4/server/config"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/fleetdm/fleet/v4/server/version"
|
|
"github.com/go-kit/log/level"
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
type statistics struct {
|
|
fleet.UpdateCreateTimestamps
|
|
Identifier string `db:"anonymous_identifier"`
|
|
}
|
|
|
|
func (ds *Datastore) ShouldSendStatistics(ctx context.Context, frequency time.Duration, config config.FleetConfig) (fleet.StatisticsPayload, bool, error) {
|
|
lic, _ := license.FromContext(ctx)
|
|
|
|
computeStats := func(stats *fleet.StatisticsPayload, since time.Time) error {
|
|
enrolledHostsByOS, amountEnrolledHosts, err := amountEnrolledHostsByOSDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount enrolled hosts by os")
|
|
}
|
|
|
|
numHostsABMPending, err := numHostsABMPendingDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount hosts that are ABM pending")
|
|
}
|
|
|
|
amountUsers, err := tableRowsCount(ctx, ds.reader(ctx), "users")
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount users")
|
|
}
|
|
amountSoftwaresVersions, err := tableRowsCount(ctx, ds.reader(ctx), "software")
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount software")
|
|
}
|
|
amountHostSoftwares, err := tableRowsCount(ctx, ds.reader(ctx), "host_software")
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount host_software")
|
|
}
|
|
amountSoftwareTitles, err := tableRowsCount(ctx, ds.reader(ctx), "software_titles")
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount software_titles")
|
|
}
|
|
amountHostSoftwareInstalledPaths, err := tableRowsCount(ctx, ds.reader(ctx), "host_software_installed_paths")
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount host_software_installed_paths")
|
|
}
|
|
amountSoftwareCpes, err := tableRowsCount(ctx, ds.reader(ctx), "software_cpe")
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount software_cpe")
|
|
}
|
|
amountSoftwareCves, err := tableRowsCount(ctx, ds.reader(ctx), "software_cve")
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount software_cve")
|
|
}
|
|
amountTeams, err := amountTeamsDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount teams")
|
|
}
|
|
amountPolicies, err := amountPoliciesDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount policies")
|
|
}
|
|
amountLabels, err := amountLabelsDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount labels")
|
|
}
|
|
appConfig, err := ds.AppConfig(ctx)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "statistics app config")
|
|
}
|
|
amountWeeklyUsers, err := amountActiveUsersSinceDB(ctx, ds.reader(ctx), since)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount active users")
|
|
}
|
|
amountPolicyViolationDaysActual, amountPolicyViolationDaysPossible, err := amountPolicyViolationDaysDB(ctx, ds.reader(ctx))
|
|
if err == sql.ErrNoRows {
|
|
level.Debug(ds.logger).Log("msg", "amount policy violation days", "err", err) //nolint:errcheck
|
|
} else if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount policy violation days")
|
|
}
|
|
storedErrs, err := ctxerr.Aggregate(ctx)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "statistics error store")
|
|
}
|
|
amountHostsNotResponding, err := countHostsNotRespondingDB(ctx, ds.reader(ctx), ds.logger, config)
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount hosts not responding")
|
|
}
|
|
amountHostsByOrbitVersion, err := amountHostsByOrbitVersionDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount hosts by orbit version")
|
|
}
|
|
amountHostsByOsqueryVersion, err := amountHostsByOsqueryVersionDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "amount hosts by osquery version")
|
|
}
|
|
numHostsFleetDesktopEnabled, err := numHostsFleetDesktopEnabledDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "number of hosts with Fleet desktop installed")
|
|
}
|
|
numQueries, err := numSavedQueriesDB(ctx, ds.reader(ctx))
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "number of saved queries in DB")
|
|
}
|
|
|
|
stats.NumHostsEnrolled = amountEnrolledHosts
|
|
stats.NumHostsABMPending = numHostsABMPending
|
|
stats.NumUsers = amountUsers
|
|
stats.NumSoftwareVersions = amountSoftwaresVersions
|
|
stats.NumHostSoftwares = amountHostSoftwares
|
|
stats.NumSoftwareTitles = amountSoftwareTitles
|
|
stats.NumHostSoftwareInstalledPaths = amountHostSoftwareInstalledPaths
|
|
stats.NumSoftwareCPEs = amountSoftwareCpes
|
|
stats.NumSoftwareCVEs = amountSoftwareCves
|
|
stats.NumTeams = amountTeams
|
|
stats.NumPolicies = amountPolicies
|
|
stats.NumLabels = amountLabels
|
|
stats.SoftwareInventoryEnabled = appConfig.Features.EnableSoftwareInventory
|
|
stats.VulnDetectionEnabled = config.Vulnerabilities.DatabasesPath != "" || appConfig.VulnerabilitySettings.DatabasesPath != ""
|
|
stats.SystemUsersEnabled = appConfig.Features.EnableHostUsers
|
|
stats.HostsStatusWebHookEnabled = appConfig.WebhookSettings.HostStatusWebhook.Enable
|
|
stats.MDMMacOsEnabled = appConfig.MDM.EnabledAndConfigured
|
|
stats.HostExpiryEnabled = appConfig.HostExpirySettings.HostExpiryEnabled
|
|
stats.MDMWindowsEnabled = appConfig.MDM.WindowsEnabledAndConfigured
|
|
stats.LiveQueryDisabled = appConfig.ServerSettings.LiveQueryDisabled
|
|
stats.NumWeeklyActiveUsers = amountWeeklyUsers
|
|
stats.NumWeeklyPolicyViolationDaysActual = amountPolicyViolationDaysActual
|
|
stats.NumWeeklyPolicyViolationDaysPossible = amountPolicyViolationDaysPossible
|
|
stats.HostsEnrolledByOperatingSystem = enrolledHostsByOS
|
|
stats.HostsEnrolledByOrbitVersion = amountHostsByOrbitVersion
|
|
stats.HostsEnrolledByOsqueryVersion = amountHostsByOsqueryVersion
|
|
stats.StoredErrors = storedErrs
|
|
stats.NumHostsNotResponding = amountHostsNotResponding
|
|
stats.Organization = "unknown"
|
|
if lic != nil && lic.IsPremium() {
|
|
stats.Organization = lic.Organization
|
|
}
|
|
stats.AIFeaturesDisabled = appConfig.ServerSettings.AIFeaturesDisabled
|
|
stats.MaintenanceWindowsConfigured = len(appConfig.Integrations.GoogleCalendar) > 0 && appConfig.Integrations.GoogleCalendar[0].Domain != "" && len(appConfig.Integrations.GoogleCalendar[0].ApiKey) > 0
|
|
|
|
stats.MaintenanceWindowsEnabled = false
|
|
teams, err := ds.ListTeams(ctx, fleet.TeamFilter{User: &fleet.User{
|
|
GlobalRole: ptr.String(fleet.RoleAdmin),
|
|
}}, fleet.ListOptions{})
|
|
if err != nil {
|
|
return ctxerr.Wrap(ctx, err, "list teams")
|
|
}
|
|
for _, team := range teams {
|
|
if team.Config.Integrations.GoogleCalendar != nil && team.Config.Integrations.GoogleCalendar.Enable {
|
|
stats.MaintenanceWindowsEnabled = true
|
|
break
|
|
}
|
|
}
|
|
stats.NumHostsFleetDesktopEnabled = numHostsFleetDesktopEnabled
|
|
stats.NumQueries = numQueries
|
|
return nil
|
|
}
|
|
|
|
dest := statistics{}
|
|
err := sqlx.GetContext(ctx, ds.reader(ctx), &dest, `SELECT created_at, updated_at, anonymous_identifier FROM statistics LIMIT 1`)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
anonIdentifier, err := server.GenerateRandomText(64)
|
|
if err != nil {
|
|
return fleet.StatisticsPayload{}, false, ctxerr.Wrap(ctx, err, "generate random text")
|
|
}
|
|
_, err = ds.writer(ctx).ExecContext(ctx, `INSERT INTO statistics(anonymous_identifier) VALUES (?)`, anonIdentifier)
|
|
if err != nil {
|
|
return fleet.StatisticsPayload{}, false, ctxerr.Wrap(ctx, err, "insert statistics")
|
|
}
|
|
|
|
// compute active weekly users since now - frequency
|
|
stats := fleet.StatisticsPayload{
|
|
AnonymousIdentifier: anonIdentifier,
|
|
FleetVersion: version.Version().Version,
|
|
LicenseTier: fleet.TierFree,
|
|
}
|
|
if lic != nil {
|
|
stats.LicenseTier = lic.Tier
|
|
}
|
|
if err := computeStats(&stats, time.Now().Add(-frequency)); err != nil {
|
|
return fleet.StatisticsPayload{}, false, ctxerr.Wrap(ctx, err, "compute statistics")
|
|
}
|
|
|
|
return stats, true, nil
|
|
}
|
|
return fleet.StatisticsPayload{}, false, ctxerr.Wrap(ctx, err, "get statistics")
|
|
}
|
|
|
|
lastUpdated := dest.UpdatedAt
|
|
if dest.CreatedAt.After(dest.UpdatedAt) {
|
|
lastUpdated = dest.CreatedAt
|
|
}
|
|
if time.Now().Before(lastUpdated.Add(frequency)) {
|
|
return fleet.StatisticsPayload{}, false, nil
|
|
}
|
|
|
|
stats := fleet.StatisticsPayload{
|
|
AnonymousIdentifier: dest.Identifier,
|
|
FleetVersion: version.Version().Version,
|
|
LicenseTier: fleet.TierFree,
|
|
}
|
|
if lic != nil {
|
|
stats.LicenseTier = lic.Tier
|
|
}
|
|
if err := computeStats(&stats, lastUpdated); err != nil {
|
|
return fleet.StatisticsPayload{}, false, ctxerr.Wrap(ctx, err, "compute statistics")
|
|
}
|
|
|
|
return stats, true, nil
|
|
}
|
|
|
|
func (ds *Datastore) RecordStatisticsSent(ctx context.Context) error {
|
|
_, err := ds.writer(ctx).ExecContext(ctx, `UPDATE statistics SET updated_at = CURRENT_TIMESTAMP LIMIT 1`)
|
|
return ctxerr.Wrap(ctx, err, "update statistics")
|
|
}
|
|
|
|
func (ds *Datastore) CleanupStatistics(ctx context.Context) error {
|
|
// reset weekly count of policy violation days
|
|
if err := ds.InitializePolicyViolationDays(ctx); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|