diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index bfd72636ca..af0246aed9 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -16,6 +16,7 @@ import ( "github.com/WatchBeam/clock" "github.com/e-dard/netbug" + "github.com/fleetdm/fleet/ee/licensing" "github.com/fleetdm/fleet/server/config" "github.com/fleetdm/fleet/server/datastore/mysql" "github.com/fleetdm/fleet/server/datastore/s3" @@ -71,6 +72,14 @@ the way that the Fleet server works. applyDevFlags(&config) } + license, err := licensing.LoadLicense(config.License.Key) + if err != nil { + initFatal( + err, + "failed to load license", + ) + } + var logger kitlog.Logger { output := os.Stderr @@ -137,7 +146,6 @@ the way that the Fleet server works. var ds kolide.Datastore var carveStore kolide.CarveStore - var err error mailService := mail.NewService() ds, err = mysql.New(config.Mysql, clock.C, mysql.Logger(logger)) @@ -220,7 +228,7 @@ the way that the Fleet server works. liveQueryStore := live_query.NewRedisLiveQuery(redisPool) ssoSessionStore := sso.NewSessionStore(redisPool) - svc, err := service.NewService(ds, resultStore, logger, config, mailService, clock.C, ssoSessionStore, liveQueryStore, carveStore) + svc, err := service.NewService(ds, resultStore, logger, config, mailService, clock.C, ssoSessionStore, liveQueryStore, carveStore, *license) if err != nil { initFatal(err, "initializing service") } diff --git a/ee/licensing/licensing.go b/ee/licensing/licensing.go new file mode 100644 index 0000000000..0904ff2286 --- /dev/null +++ b/ee/licensing/licensing.go @@ -0,0 +1,15 @@ +package licensing + +import ( + "github.com/fleetdm/fleet/server/kolide" +) + +func LoadLicense(licenseKey string) (*kolide.LicenseInfo, error) { + // TODO actual logic here + + if licenseKey == "" { + return &kolide.LicenseInfo{Tier: "core"}, nil + } + + return &kolide.LicenseInfo{Tier: "basic"}, nil +} diff --git a/server/config/config.go b/server/config/config.go index 2ec2c535dd..aec54fd631 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -155,6 +155,11 @@ type FilesystemConfig struct { EnableLogCompression bool `yaml:"enable_log_compression"` } +// LicenseConfig defines configs related to licensing Fleet. +type LicenseConfig struct { + Key string `yaml:"key"` +} + // KolideConfig stores the application configuration. Each subcategory is // broken up into it's own struct, defined above. When editing any of these // structs, Manager.addConfigs and Manager.LoadConfig should be @@ -174,6 +179,7 @@ type KolideConfig struct { S3 S3Config PubSub PubSubConfig Filesystem FilesystemConfig + License LicenseConfig } // addConfigs adds the configuration keys and default values that will be @@ -342,6 +348,9 @@ func (man Manager) addConfigs() { "Enable automatic rotation for osquery log files") man.addConfigBool("filesystem.enable_log_compression", false, "Enable compression for the rotated osquery log files") + + // License + man.addConfigString("license.key", "", "Fleet license key (to enable Fleet Basic features)") } // LoadConfig will load the config variables into a fully initialized @@ -479,6 +488,9 @@ func (man Manager) LoadConfig() KolideConfig { EnableLogRotation: man.getConfigBool("filesystem.enable_log_rotation"), EnableLogCompression: man.getConfigBool("filesystem.enable_log_compression"), }, + License: LicenseConfig{ + Key: man.getConfigString("license.key"), + }, } } diff --git a/server/kolide/app.go b/server/kolide/app.go index cfade19d6a..fcc50acdda 100644 --- a/server/kolide/app.go +++ b/server/kolide/app.go @@ -47,6 +47,9 @@ type AppConfigService interface { // Version returns version and build information. Version(ctx context.Context) (*version.Info, error) + + // License returns the licensing information. + License(ctx context.Context) (*LicenseInfo, error) } // SMTP settings names returned from API, these map to SMTPAuthType and @@ -328,3 +331,15 @@ type EnrollSecretSpec struct { // Secrets is the list of enroll secrets. Secrets []EnrollSecret `json:"secrets"` } + +// LicenseInfo contains information about the Fleet license. +type LicenseInfo struct { + // Tier is the license tier (currently "core" or "basic") + Tier string `json:"tier"` + // Organization is the name of the licensed organization. + Organization string `json:"organization,omitempty"` + // DeviceCount is the number of licensed devices. + DeviceCount int `json:"device_count,omitempty"` + // Expiration is when the license expires. + Expiration *time.Time `json:"expiration,omitempty"` +} diff --git a/server/service/endpoint_appconfig.go b/server/service/endpoint_appconfig.go index c557182ec5..6a526921df 100644 --- a/server/service/endpoint_appconfig.go +++ b/server/service/endpoint_appconfig.go @@ -21,6 +21,7 @@ type appConfigResponse struct { SSOSettings *kolide.SSOSettingsPayload `json:"sso_settings,omitempty"` HostExpirySettings *kolide.HostExpirySettings `json:"host_expiry_settings,omitempty"` HostSettings *kolide.HostSettings `json:"host_settings,omitempty"` + License *kolide.LicenseInfo `json:"license,omitempty"` Err error `json:"error,omitempty"` } @@ -36,6 +37,10 @@ func makeGetAppConfigEndpoint(svc kolide.Service) endpoint.Endpoint { if err != nil { return nil, err } + license, err := svc.License(ctx) + if err != nil { + return nil, err + } var smtpSettings *kolide.SMTPSettingsPayload var ssoSettings *kolide.SSOSettingsPayload var hostExpirySettings *kolide.HostExpirySettings @@ -75,6 +80,7 @@ func makeGetAppConfigEndpoint(svc kolide.Service) endpoint.Endpoint { HostSettings: &kolide.HostSettings{ AdditionalQueries: config.AdditionalQueries, }, + License: license, } return response, nil } diff --git a/server/service/service.go b/server/service/service.go index e3a0cfd6f2..d3fceeda51 100644 --- a/server/service/service.go +++ b/server/service/service.go @@ -22,7 +22,8 @@ import ( // NewService creates a new service from the config struct func NewService(ds kolide.Datastore, resultStore kolide.QueryResultStore, logger kitlog.Logger, config config.KolideConfig, mailService kolide.MailService, - c clock.Clock, sso sso.SessionStore, lq kolide.LiveQueryStore, carveStore kolide.CarveStore) (kolide.Service, error) { + c clock.Clock, sso sso.SessionStore, lq kolide.LiveQueryStore, carveStore kolide.CarveStore, + license kolide.LicenseInfo) (kolide.Service, error) { var svc kolide.Service osqueryLogger, err := logging.New(config, logger) @@ -45,6 +46,7 @@ func NewService(ds kolide.Datastore, resultStore kolide.QueryResultStore, metaDataClient: &http.Client{ Timeout: 5 * time.Second, }, + license: license, } svc = validationMiddleware{svc, ds, sso} return svc, nil @@ -66,6 +68,8 @@ type service struct { metaDataClient *http.Client seenHostSet *seenHostSet + + license kolide.LicenseInfo } func (s service) SendEmail(mail kolide.Email) error { diff --git a/server/service/service_appconfig.go b/server/service/service_appconfig.go index 3fd91908fe..bc2bb661f4 100644 --- a/server/service/service_appconfig.go +++ b/server/service/service_appconfig.go @@ -265,3 +265,7 @@ func (svc service) Version(ctx context.Context) (*version.Info, error) { info := version.Version() return &info, nil } + +func (svc service) License(ctx context.Context) (*kolide.LicenseInfo, error) { + return &svc.license, nil +} diff --git a/server/service/util_test.go b/server/service/util_test.go index fa952ccd9f..34ea6703a9 100644 --- a/server/service/util_test.go +++ b/server/service/util_test.go @@ -13,12 +13,14 @@ import ( func newTestService(ds kolide.Datastore, rs kolide.QueryResultStore, lq kolide.LiveQueryStore) (kolide.Service, error) { mailer := &mockMailService{SendEmailFn: func(e kolide.Email) error { return nil }} - return NewService(ds, rs, kitlog.NewNopLogger(), config.TestConfig(), mailer, clock.C, nil, lq, ds) + license := kolide.LicenseInfo{Tier: "core"} + return NewService(ds, rs, kitlog.NewNopLogger(), config.TestConfig(), mailer, clock.C, nil, lq, ds, license) } func newTestServiceWithClock(ds kolide.Datastore, rs kolide.QueryResultStore, lq kolide.LiveQueryStore, c clock.Clock) (kolide.Service, error) { mailer := &mockMailService{SendEmailFn: func(e kolide.Email) error { return nil }} - return NewService(ds, rs, kitlog.NewNopLogger(), config.TestConfig(), mailer, c, nil, lq, ds) + license := kolide.LicenseInfo{Tier: "core"} + return NewService(ds, rs, kitlog.NewNopLogger(), config.TestConfig(), mailer, c, nil, lq, ds, license) } func createTestAppConfig(t *testing.T, ds kolide.Datastore) *kolide.AppConfig {