mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-13 14:05:42 +08:00
* Partial implementation of Bootstrap Token
Fetching the token from the db and sending it back to the client when the client asks for it ie sends the GetBootstrapTokenRequest is not implemented
Also earlier code for Bootstrap Token from commit 58c3782 "mdm: add SetBootstrapToken command." is removed
* implements rest of the bootstrap token feature - micromdm now sends bootstrap token back to the client, when client requests it
* regenerate some proto files
* use original proto generator versions
checkin.pb.go: golang/protobuf@v1.2.0
mdm.pb.go: gogo/protobuf@v1.3.2
device.pb.go: golang/protobuf@v1.0.0
* cleanup syntax
* use more idiomatic naming
* clean up BootstrapToken storage
* add GetBootstrapToken test
* remove extra newline
Co-authored-by: Jesse Peterson <jessepeterson@users.noreply.github.com>
* add doc comments
Co-authored-by: Ilkka Vanhatalo <ilkka.vanhatalo@helsinki.fi>
Co-authored-by: Jesse Peterson <jessepeterson@users.noreply.github.com>
384 lines
9.5 KiB
Go
384 lines
9.5 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/x509"
|
|
"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"
|
|
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
|
|
|
|
APNSPushService apns.Service
|
|
CommandService command.Service
|
|
MDMService mdm.Service
|
|
EnrollService enroll.Service
|
|
SCEPService scep.Service
|
|
ConfigService config.Service
|
|
|
|
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.setupCommandService(); 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.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)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.CommandService = commandService
|
|
return nil
|
|
}
|
|
|
|
func (c *Server) setupCommandQueue(logger log.Logger) error {
|
|
opts := []queue.Option{queue.WithLogger(logger)}
|
|
if c.NoCmdHistory {
|
|
opts = append(opts, queue.WithoutHistory())
|
|
}
|
|
q, err := queue.NewQueue(c.DB, c.PubClient, opts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
devDB, err := devicebuiltin.NewDB(c.DB)
|
|
if err != nil {
|
|
return errors.Wrap(err, "new device db")
|
|
}
|
|
|
|
var mdmService mdm.Service
|
|
{
|
|
svc := mdm.NewService(c.PubClient, q, devDB)
|
|
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.CSRSigner = 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.ChallengeMiddleware(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
|
|
}
|