Files
micromdm/server/server.go
Jesse Peterson 72487f8c1e Remove SCEP CA certificate from both being stored on disk and being included in the enrollment profile (#490)
The SCEP CA certificate is not needed to establish trust between the client and MDM server. Thus including it in the enrollment profile is unnecessary. Separately, and since we don't need it, we can clean up the enrollment service a little by not reading the certificate from disk. Finally — just don't write it out to disk at all. Its usefulness on disk (for the sake of being in its own file) is debatable.
2018-08-23 10:20:47 -04:00

447 lines
10 KiB
Go

package server
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"time"
"github.com/RobotsAndPencils/buford/push"
"github.com/boltdb/bolt"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/micromdm/dep"
boltdepot "github.com/micromdm/scep/depot/bolt"
scep "github.com/micromdm/scep/server"
"github.com/pkg/errors"
"golang.org/x/crypto/pkcs12"
"github.com/micromdm/micromdm/dep/depsync"
"github.com/micromdm/micromdm/mdm"
"github.com/micromdm/micromdm/mdm/enroll"
"github.com/micromdm/micromdm/pkg/crypto"
"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/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"
)
type Server struct {
ConfigPath string
Depsim string
PubClient pubsub.PublishSubscriber
DB *bolt.DB
PushCert pushServiceCert
ServerPublicURL string
SCEPChallenge string
APNSPrivateKeyPath string
APNSCertificatePath string
APNSPrivateKeyPass string
TLSCertPath string
SCEPDepot *boltdepot.Depot
ProfileDB profile.Store
ConfigDB config.Store
RemoveDB block.Store
CommandWebhookURL string
DEPClient dep.Client
PushService *push.Service // bufford push
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.loadPushCerts(); 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
}
if err := c.setupEnrollmentService(); err != nil {
return err
}
return nil
}
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 {
q, err := queue.NewQueue(c.DB, c.PubClient)
if err != nil {
return err
}
var mdmService mdm.Service
{
svc := mdm.NewService(c.PubClient, q)
mdmService = svc
mdmService = block.RemoveMiddleware(c.RemoveDB)(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
}
func (c *Server) loadPushCerts() error {
if c.APNSCertificatePath == "" && c.APNSPrivateKeyPass == "" && c.APNSPrivateKeyPath == "" {
// this is optional, config could also be provided with mdmctl
return nil
}
var err error
if c.APNSPrivateKeyPath == "" {
var pkcs12Data []byte
pkcs12Data, err = ioutil.ReadFile(c.APNSCertificatePath)
if err != nil {
return err
}
c.PushCert.PrivateKey, c.PushCert.Certificate, err =
pkcs12.Decode(pkcs12Data, c.APNSPrivateKeyPass)
return err
}
c.PushCert.Certificate, err = crypto.ReadPEMCertificateFile(c.APNSCertificatePath)
if err != nil {
return err
}
var pemData []byte
pemData, err = ioutil.ReadFile(c.APNSPrivateKeyPath)
if err != nil {
return err
}
pkeyBlock := new(bytes.Buffer)
pemBlock, _ := pem.Decode(pemData)
if pemBlock == nil {
return errors.New("invalid PEM data for privkey")
}
if x509.IsEncryptedPEMBlock(pemBlock) {
b, err := x509.DecryptPEMBlock(pemBlock, []byte(c.APNSPrivateKeyPass))
if err != nil {
return fmt.Errorf("decrypting DES private key %s", err)
}
pkeyBlock.Write(b)
} else {
pkeyBlock.Write(pemBlock.Bytes)
}
priv, err := x509.ParsePKCS1PrivateKey(pkeyBlock.Bytes())
if err != nil {
return fmt.Errorf("parsing pkcs1 private key: %s", err)
}
c.PushCert.PrivateKey = priv
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 {
var opts []apns.Option
{
cert, _ := c.ConfigDB.PushCertificate()
if c.PushCert.Certificate != nil && cert == nil {
cert = &tls.Certificate{
Certificate: [][]byte{c.PushCert.Certificate.Raw},
PrivateKey: c.PushCert.PrivateKey,
Leaf: c.PushCert.Certificate,
}
}
if cert == nil {
goto after
}
client, err := push.NewClient(*cert)
if err != nil {
return err
}
svc := push.NewService(client, push.Production)
opts = append(opts, apns.WithPushService(svc))
}
after:
db, err := apnsbuiltin.NewDB(c.DB, c.PubClient)
if err != nil {
return err
}
service, err := apns.New(db, c.ConfigDB, c.PubClient, opts...)
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 topicProvider enroll.TopicProvider
var err error
if c.PushCert.Certificate != nil {
pushTopic, err := crypto.TopicFromCert(c.PushCert.Certificate)
if err != nil {
return errors.Wrap(err, "get apns topic from certificate")
}
topicProvider = staticTopicProvider{topic: pushTopic}
} else {
topicProvider = c.ConfigDB
}
var SCEPCertificateSubject string
// 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(
topicProvider,
c.PubClient,
c.ServerPublicURL+"/scep",
c.SCEPChallenge,
c.ServerPublicURL,
c.TLSCertPath,
SCEPCertificateSubject,
c.ProfileDB,
)
if err != nil {
return err
}
return nil
}
// if the apns-cert flags are specified this provider will be used in the enroll service.
type staticTopicProvider struct{ topic string }
func (p staticTopicProvider) PushTopic() (string, error) {
return p.topic, nil
}
func (c *Server) setupDepClient() error {
// depsim config
depsim := c.Depsim
var conf *dep.Config
// try getting the oauth config from bolt
tokens, err := c.ConfigDB.DEPTokens()
if err != nil {
return err
}
if len(tokens) >= 1 {
conf = new(dep.Config)
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 != "" {
conf = &dep.Config{
ConsumerKey: "CK_48dd68d198350f51258e885ce9a5c37ab7f98543c4a697323d75682a6c10a32501cb247e3db08105db868f73f2c972bdb6ae77112aea803b9219eb52689d42e6",
ConsumerSecret: "CS_34c7b2b531a600d99a0e4edcf4a78ded79b86ef318118c2f5bcfee1b011108c32d5302df801adbe29d446eb78f02b13144e323eb9aad51c79f01e50cb45c3a68",
AccessToken: "AT_927696831c59ba510cfe4ec1a69e5267c19881257d4bca2906a99d0785b785a6f6fdeb09774954fdd5e2d0ad952e3af52c6d8d2f21c924ba0caf4a031c158b89",
AccessSecret: "AS_c31afd7a09691d83548489336e8ff1cb11b82b6bca13f793344496a556b1f4972eaff4dde6deb5ac9cf076fdfa97ec97699c34d515947b9cf9ed31c99dded6ba",
}
}
if conf == nil {
return nil
}
depServerURL := "https://mdmenrollment.apple.com"
if depsim != "" {
depServerURL = depsim
}
client, err := dep.NewClient(conf, dep.ServerURL(depServerURL))
if err != nil {
return err
}
c.DEPClient = client
return nil
}
func (c *Server) CreateDEPSyncer(logger log.Logger) (depsync.Syncer, error) {
client := c.DEPClient
opts := []depsync.Option{
depsync.WithLogger(log.With(logger, "component", "depsync")),
}
if client != nil {
opts = append(opts, depsync.WithClient(client))
}
var syncer depsync.Syncer
syncer, err := depsync.New(c.PubClient, c.DB, logger, opts...)
if err != nil {
return nil, err
}
return syncer, nil
}
func (c *Server) setupSCEP(logger log.Logger) error {
depot, err := boltdepot.NewBoltDepot(c.DB)
if err != nil {
return err
}
key, err := depot.CreateOrLoadKey(2048)
if err != nil {
return err
}
_, err = depot.CreateOrLoadCA(key, 5, "MicroMDM", "US")
if err != nil {
return err
}
opts := []scep.ServiceOption{
scep.ClientValidity(365),
scep.ChallengePassword(c.SCEPChallenge),
}
c.SCEPDepot = depot
c.SCEPService, err = scep.NewService(depot, opts...)
if err != nil {
return err
}
c.SCEPService = scep.NewLoggingService(logger, c.SCEPService)
return nil
}