package service import ( "context" "encoding/json" "fmt" "html/template" "strings" "github.com/fleetdm/fleet/v4/server" "github.com/fleetdm/fleet/v4/server/contexts/viewer" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mail" "github.com/kolide/kit/version" "github.com/pkg/errors" ) // mailError is set when an error performing mail operations type mailError struct { message string } func (e mailError) Error() string { return fmt.Sprintf("a mail error occurred: %s", e.message) } func (e mailError) MailError() []map[string]string { return []map[string]string{ { "name": "base", "reason": e.message, }, } } func (svc *Service) NewAppConfig(ctx context.Context, p fleet.AppConfig) (*fleet.AppConfig, error) { // skipauth: No user context yet when the app config is first created. svc.authz.SkipAuthorization(ctx) newConfig, err := svc.ds.NewAppConfig(&p) if err != nil { return nil, err } // Set up a default enroll secret secret, err := server.GenerateRandomText(fleet.EnrollSecretDefaultLength) if err != nil { return nil, errors.Wrap(err, "generate enroll secret string") } secrets := []*fleet.EnrollSecret{ { Secret: secret, }, } err = svc.ds.ApplyEnrollSecrets(nil, secrets) if err != nil { return nil, errors.Wrap(err, "save enroll secret") } return newConfig, nil } func (svc *Service) AppConfig(ctx context.Context) (*fleet.AppConfig, error) { if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil { return nil, err } return svc.ds.AppConfig() } func (svc *Service) sendTestEmail(ctx context.Context, config *fleet.AppConfig) error { vc, ok := viewer.FromContext(ctx) if !ok { return fleet.ErrNoContext } testMail := fleet.Email{ Subject: "Hello from Fleet", To: []string{vc.User.Email}, Mailer: &mail.SMTPTestMailer{ BaseURL: template.URL(config.ServerSettings.ServerURL + svc.config.Server.URLPrefix), AssetURL: getAssetURL(), }, Config: config, } if err := mail.Test(svc.mailService, testMail); err != nil { return mailError{message: err.Error()} } return nil } func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte) (*fleet.AppConfig, error) { if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionWrite); err != nil { return nil, err } appConfig, err := svc.AppConfig(ctx) if err != nil { return nil, err } // We apply the config that is incoming to the old one err = json.Unmarshal(p, &appConfig) if err != nil { return nil, err } if appConfig.SMTPSettings.SMTPEnabled || appConfig.SMTPSettings.SMTPConfigured { if err = svc.sendTestEmail(ctx, appConfig); err != nil { return nil, err } appConfig.SMTPSettings.SMTPConfigured = true } else if appConfig.SMTPSettings.SMTPEnabled { appConfig.SMTPSettings.SMTPConfigured = false } if err := svc.ds.SaveAppConfig(appConfig); err != nil { return nil, err } return appConfig, nil } func cleanupURL(url string) string { return strings.TrimRight(strings.Trim(url, " \t\n"), "/") } func (svc *Service) ApplyEnrollSecretSpec(ctx context.Context, spec *fleet.EnrollSecretSpec) error { if err := svc.authz.Authorize(ctx, &fleet.EnrollSecret{}, fleet.ActionWrite); err != nil { return err } for _, s := range spec.Secrets { if s.Secret == "" { return errors.New("enroll secret must not be empty") } } return svc.ds.ApplyEnrollSecrets(nil, spec.Secrets) } func (svc *Service) GetEnrollSecretSpec(ctx context.Context) (*fleet.EnrollSecretSpec, error) { if err := svc.authz.Authorize(ctx, &fleet.EnrollSecret{}, fleet.ActionRead); err != nil { return nil, err } secrets, err := svc.ds.GetEnrollSecrets(nil) if err != nil { return nil, err } return &fleet.EnrollSecretSpec{Secrets: secrets}, nil } func (svc *Service) Version(ctx context.Context) (*version.Info, error) { if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil { return nil, err } info := version.Version() return &info, nil } func (svc *Service) License(ctx context.Context) (*fleet.LicenseInfo, error) { if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil { return nil, err } return &svc.license, nil } func (svc *Service) SetupRequired(ctx context.Context) (bool, error) { users, err := svc.ds.ListUsers(fleet.UserListOptions{ListOptions: fleet.ListOptions{Page: 0, PerPage: 1}}) if err != nil { return false, err } if len(users) == 0 { return true, nil } return false, nil } func (svc *Service) LoggingConfig(ctx context.Context) (*fleet.Logging, error) { if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil { return nil, err } conf := svc.config logging := &fleet.Logging{ Debug: conf.Logging.Debug, Json: conf.Logging.JSON, } switch conf.Osquery.StatusLogPlugin { case "", "filesystem": logging.Status = fleet.LoggingPlugin{ Plugin: "filesystem", Config: fleet.FilesystemConfig{FilesystemConfig: conf.Filesystem}, } case "kinesis": logging.Status = fleet.LoggingPlugin{ Plugin: "kinesis", Config: fleet.KinesisConfig{ Region: conf.Kinesis.Region, StatusStream: conf.Kinesis.StatusStream, ResultStream: conf.Kinesis.ResultStream, }, } case "firehose": logging.Status = fleet.LoggingPlugin{ Plugin: "firehose", Config: fleet.FirehoseConfig{ Region: conf.Firehose.Region, StatusStream: conf.Firehose.StatusStream, ResultStream: conf.Firehose.ResultStream, }, } case "lambda": logging.Status = fleet.LoggingPlugin{ Plugin: "lambda", Config: fleet.LambdaConfig{ Region: conf.Lambda.Region, StatusFunction: conf.Lambda.StatusFunction, ResultFunction: conf.Lambda.ResultFunction, }, } case "pubsub": logging.Status = fleet.LoggingPlugin{ Plugin: "pubsub", Config: fleet.PubSubConfig{PubSubConfig: conf.PubSub}, } case "stdout": logging.Status = fleet.LoggingPlugin{Plugin: "stdout"} default: return nil, errors.Errorf("unrecognized logging plugin: %s", conf.Osquery.StatusLogPlugin) } switch conf.Osquery.ResultLogPlugin { case "", "filesystem": logging.Result = fleet.LoggingPlugin{ Plugin: "filesystem", Config: fleet.FilesystemConfig{FilesystemConfig: conf.Filesystem}, } case "kinesis": logging.Result = fleet.LoggingPlugin{ Plugin: "kinesis", Config: fleet.KinesisConfig{ Region: conf.Kinesis.Region, StatusStream: conf.Kinesis.StatusStream, ResultStream: conf.Kinesis.ResultStream, }, } case "firehose": logging.Result = fleet.LoggingPlugin{ Plugin: "firehose", Config: fleet.FirehoseConfig{ Region: conf.Firehose.Region, StatusStream: conf.Firehose.StatusStream, ResultStream: conf.Firehose.ResultStream, }, } case "lambda": logging.Result = fleet.LoggingPlugin{ Plugin: "lambda", Config: fleet.LambdaConfig{ Region: conf.Lambda.Region, StatusFunction: conf.Lambda.StatusFunction, ResultFunction: conf.Lambda.ResultFunction, }, } case "pubsub": logging.Result = fleet.LoggingPlugin{ Plugin: "pubsub", Config: fleet.PubSubConfig{PubSubConfig: conf.PubSub}, } case "stdout": logging.Result = fleet.LoggingPlugin{ Plugin: "stdout", } default: return nil, errors.Errorf("unrecognized logging plugin: %s", conf.Osquery.ResultLogPlugin) } return logging, nil }