mirror of
https://github.com/micromdm/micromdm/
synced 2026-05-13 01:46:05 +08:00
412 lines
10 KiB
Go
412 lines
10 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/micromdm/micromdm/dep"
|
|
"github.com/micromdm/micromdm/mdm"
|
|
"github.com/micromdm/micromdm/mdm/enroll"
|
|
"github.com/micromdm/micromdm/platform/apns"
|
|
apnsbuiltin "github.com/micromdm/micromdm/platform/apns/builtin"
|
|
"github.com/micromdm/micromdm/platform/command"
|
|
"github.com/micromdm/micromdm/platform/config"
|
|
configbuiltin "github.com/micromdm/micromdm/platform/config/builtin"
|
|
"github.com/micromdm/micromdm/platform/dep/sync"
|
|
syncbuiltin "github.com/micromdm/micromdm/platform/dep/sync/builtin"
|
|
"github.com/micromdm/micromdm/platform/device"
|
|
devicebuiltin "github.com/micromdm/micromdm/platform/device/builtin"
|
|
"github.com/micromdm/micromdm/platform/profile"
|
|
profilebuiltin "github.com/micromdm/micromdm/platform/profile/builtin"
|
|
"github.com/micromdm/micromdm/platform/pubsub"
|
|
"github.com/micromdm/micromdm/platform/pubsub/inmem"
|
|
"github.com/micromdm/micromdm/platform/queue"
|
|
queueinmem "github.com/micromdm/micromdm/platform/queue/inmem"
|
|
block "github.com/micromdm/micromdm/platform/remove"
|
|
blockbuiltin "github.com/micromdm/micromdm/platform/remove/builtin"
|
|
"github.com/micromdm/micromdm/workflow/webhook"
|
|
|
|
"github.com/boltdb/bolt"
|
|
"github.com/go-kit/kit/log"
|
|
"github.com/go-kit/kit/log/level"
|
|
"github.com/micromdm/scep/v2/challenge"
|
|
boltchallenge "github.com/micromdm/scep/v2/challenge/bolt"
|
|
"github.com/micromdm/scep/v2/depot"
|
|
boltdepot "github.com/micromdm/scep/v2/depot/bolt"
|
|
scep "github.com/micromdm/scep/v2/server"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type Server struct {
|
|
ConfigPath string
|
|
Depsim string
|
|
PubClient pubsub.PublishSubscriber
|
|
DB *bolt.DB
|
|
ServerPublicURL string
|
|
SCEPChallenge string
|
|
SCEPClientValidity int
|
|
TLSCertPath string
|
|
SCEPDepot depot.Depot
|
|
UseDynSCEPChallenge bool
|
|
GenDynSCEPChallenge bool
|
|
SCEPChallengeDepot challenge.Store
|
|
ProfileDB profile.Store
|
|
ConfigDB config.Store
|
|
RemoveDB block.Store
|
|
CommandWebhookURL string
|
|
DEPClient *dep.Client
|
|
SyncDB *syncbuiltin.DB
|
|
NoCmdHistory bool
|
|
ValidateSCEPIssuer bool
|
|
ValidateSCEPExpiration bool
|
|
UDIDCertAuthWarnOnly bool
|
|
Queue string
|
|
DMURL string
|
|
|
|
APNSPushService apns.Service
|
|
CommandService command.Service
|
|
MDMService mdm.Service
|
|
EnrollService enroll.Service
|
|
SCEPService scep.Service
|
|
ConfigService config.Service
|
|
|
|
CommandQueue mdm.Queue
|
|
|
|
WebhooksHTTPClient *http.Client
|
|
}
|
|
|
|
func (c *Server) Setup(logger log.Logger) error {
|
|
if err := c.setupPubSub(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupBolt(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupRemoveService(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupConfigStore(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupSCEP(logger); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupPushService(logger); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupWebhooks(logger); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupCommandQueue(logger); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupCommandService(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupDepClient(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := c.setupProfileDB(); err != nil {
|
|
return err
|
|
}
|
|
|
|
err := c.setupEnrollmentService()
|
|
|
|
return err
|
|
}
|
|
|
|
func (c *Server) setupProfileDB() error {
|
|
profileDB, err := profilebuiltin.NewDB(c.DB)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.ProfileDB = profileDB
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupPubSub() error {
|
|
c.PubClient = inmem.NewPubSub()
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupWebhooks(logger log.Logger) error {
|
|
if c.CommandWebhookURL == "" {
|
|
return nil
|
|
}
|
|
|
|
ctx := context.Background()
|
|
ww := webhook.New(c.CommandWebhookURL, c.PubClient, webhook.WithLogger(logger), webhook.WithHTTPClient(c.WebhooksHTTPClient))
|
|
go ww.Run(ctx)
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupRemoveService() error {
|
|
removeDB, err := blockbuiltin.NewDB(c.DB)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.RemoveDB = removeDB
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupCommandService() error {
|
|
commandService, err := command.New(c.PubClient, c.CommandQueue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.CommandService = commandService
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupCommandQueue(logger log.Logger) error {
|
|
var q mdm.Queue
|
|
switch c.Queue {
|
|
case "inmem":
|
|
q = queueinmem.New(c.PubClient, logger)
|
|
case "builtin":
|
|
opts := []queue.Option{queue.WithLogger(logger)}
|
|
if c.NoCmdHistory {
|
|
opts = append(opts, queue.WithoutHistory())
|
|
}
|
|
var err error
|
|
q, err = queue.NewQueue(c.DB, c.PubClient, opts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "":
|
|
return errors.New("empty command queue type")
|
|
default:
|
|
return fmt.Errorf("invalid command queue type: %s", c.Queue)
|
|
}
|
|
|
|
c.CommandQueue = q
|
|
|
|
devDB, err := devicebuiltin.NewDB(c.DB)
|
|
if err != nil {
|
|
return errors.Wrap(err, "new device db")
|
|
}
|
|
|
|
var mdmService mdm.Service
|
|
{
|
|
var dm mdm.DeclarativeManagement
|
|
if c.DMURL != "" {
|
|
dm, err = NewDeclarativeManagementHTTPCaller(c.DMURL, http.DefaultClient)
|
|
if err != nil {
|
|
return fmt.Errorf("setting up declarative management: %w", err)
|
|
}
|
|
}
|
|
|
|
svc := mdm.NewService(c.PubClient, q, devDB, dm)
|
|
mdmService = svc
|
|
mdmService = block.RemoveMiddleware(c.RemoveDB)(mdmService)
|
|
|
|
udidauthLogger := log.With(logger, "component", "udidcertauth")
|
|
mdmService = device.UDIDCertAuthMiddleware(devDB, udidauthLogger, c.UDIDCertAuthWarnOnly)(mdmService)
|
|
|
|
verifycertLogger := log.With(logger, "component", "verifycert")
|
|
mdmService = VerifyCertificateMiddleware(c.ValidateSCEPIssuer, c.ValidateSCEPExpiration, c.SCEPDepot, verifycertLogger)(mdmService)
|
|
}
|
|
c.MDMService = mdmService
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupBolt() error {
|
|
dbPath := filepath.Join(c.ConfigPath, "micromdm.db")
|
|
db, err := bolt.Open(dbPath, 0644, &bolt.Options{Timeout: time.Second})
|
|
if err != nil {
|
|
return errors.Wrap(err, "opening boltdb")
|
|
}
|
|
c.DB = db
|
|
|
|
return nil
|
|
}
|
|
|
|
type pushServiceCert struct {
|
|
*x509.Certificate
|
|
PrivateKey interface{}
|
|
}
|
|
|
|
func (c *Server) setupConfigStore() error {
|
|
db, err := configbuiltin.NewDB(c.DB, c.PubClient)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.ConfigDB = db
|
|
c.ConfigService = config.New(db)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupPushService(logger log.Logger) error {
|
|
db, err := apnsbuiltin.NewDB(c.DB, c.PubClient)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
service, err := apns.New(db, c.ConfigDB, c.PubClient)
|
|
if err != nil {
|
|
return errors.Wrap(err, "starting micromdm push service")
|
|
}
|
|
c.APNSPushService = apns.LoggingMiddleware(
|
|
log.With(level.Info(logger), "component", "apns"),
|
|
)(service)
|
|
|
|
pushinfoWorker := apns.NewWorker(db, c.PubClient, logger)
|
|
go pushinfoWorker.Run(context.Background())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupEnrollmentService() error {
|
|
var (
|
|
SCEPCertificateSubject string
|
|
err error
|
|
)
|
|
|
|
chalStore := c.SCEPChallengeDepot
|
|
if !c.GenDynSCEPChallenge {
|
|
chalStore = nil
|
|
}
|
|
|
|
// TODO: clean up order of inputs. Maybe pass *SCEPConfig as an arg?
|
|
// but if you do, the packages are coupled, better not.
|
|
c.EnrollService, err = enroll.NewService(
|
|
c.ConfigDB,
|
|
c.PubClient,
|
|
c.ServerPublicURL+"/scep",
|
|
c.SCEPChallenge,
|
|
c.ServerPublicURL,
|
|
c.TLSCertPath,
|
|
SCEPCertificateSubject,
|
|
c.ProfileDB,
|
|
chalStore,
|
|
)
|
|
return errors.Wrap(err, "setting up enrollment service")
|
|
}
|
|
|
|
func (c *Server) setupDepClient() error {
|
|
var (
|
|
conf dep.OAuthParameters
|
|
depsim = c.Depsim
|
|
hasTokenConfig bool
|
|
opts []dep.Option
|
|
)
|
|
|
|
// try getting the oauth config from bolt
|
|
tokens, err := c.ConfigDB.DEPTokens()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(tokens) >= 1 {
|
|
hasTokenConfig = true
|
|
conf.ConsumerSecret = tokens[0].ConsumerSecret
|
|
conf.ConsumerKey = tokens[0].ConsumerKey
|
|
conf.AccessSecret = tokens[0].AccessSecret
|
|
conf.AccessToken = tokens[0].AccessToken
|
|
// TODO: handle expiration
|
|
}
|
|
|
|
// override with depsim keys if specified on CLI
|
|
if depsim != "" {
|
|
hasTokenConfig = true
|
|
conf = dep.OAuthParameters{
|
|
ConsumerKey: "CK_48dd68d198350f51258e885ce9a5c37ab7f98543c4a697323d75682a6c10a32501cb247e3db08105db868f73f2c972bdb6ae77112aea803b9219eb52689d42e6",
|
|
ConsumerSecret: "CS_34c7b2b531a600d99a0e4edcf4a78ded79b86ef318118c2f5bcfee1b011108c32d5302df801adbe29d446eb78f02b13144e323eb9aad51c79f01e50cb45c3a68",
|
|
AccessToken: "AT_927696831c59ba510cfe4ec1a69e5267c19881257d4bca2906a99d0785b785a6f6fdeb09774954fdd5e2d0ad952e3af52c6d8d2f21c924ba0caf4a031c158b89",
|
|
AccessSecret: "AS_c31afd7a09691d83548489336e8ff1cb11b82b6bca13f793344496a556b1f4972eaff4dde6deb5ac9cf076fdfa97ec97699c34d515947b9cf9ed31c99dded6ba",
|
|
}
|
|
depsimurl, err := url.Parse(depsim)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts = append(opts, dep.WithServerURL(depsimurl))
|
|
}
|
|
|
|
if !hasTokenConfig {
|
|
return nil
|
|
}
|
|
|
|
c.DEPClient = dep.NewClient(conf, opts...)
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) CreateDEPSyncer(logger log.Logger) (sync.Syncer, error) {
|
|
client := c.DEPClient
|
|
opts := []sync.Option{
|
|
sync.WithLogger(log.With(logger, "component", "depsync")),
|
|
}
|
|
if client != nil {
|
|
opts = append(opts, sync.WithClient(client))
|
|
}
|
|
|
|
syncdb, err := syncbuiltin.NewDB(c.DB)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.SyncDB = syncdb
|
|
|
|
var syncer sync.Syncer
|
|
syncer, err = sync.NewWatcher(c.SyncDB, c.PubClient, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return syncer, nil
|
|
}
|
|
|
|
func (c *Server) setupSCEP(logger log.Logger) error {
|
|
svcBoltDepot, err := boltdepot.NewBoltDepot(c.DB)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.SCEPDepot = svcBoltDepot
|
|
|
|
key, err := svcBoltDepot.CreateOrLoadKey(2048)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
crt, err := svcBoltDepot.CreateOrLoadCA(key, 5, "MicroMDM", "US")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var signer scep.CSRSignerContext = scep.SignCSRAdapter(depot.NewSigner(
|
|
c.SCEPDepot,
|
|
depot.WithAllowRenewalDays(0),
|
|
depot.WithValidityDays(c.SCEPClientValidity),
|
|
))
|
|
if c.UseDynSCEPChallenge {
|
|
c.SCEPChallengeDepot, err = boltchallenge.NewBoltDepot(c.DB)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
signer = challenge.Middleware(c.SCEPChallengeDepot, signer)
|
|
} else {
|
|
signer = scep.StaticChallengeMiddleware(c.SCEPChallenge, signer)
|
|
}
|
|
|
|
c.SCEPService, err = scep.NewService(crt, key, signer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.SCEPService = scep.NewLoggingService(logger, c.SCEPService)
|
|
|
|
return nil
|
|
}
|