From 982e4411112c6695e8f55ddf22ba0f4b81412dec Mon Sep 17 00:00:00 2001 From: Victor Vrantchan Date: Sun, 10 Dec 2017 00:39:04 -0500 Subject: [PATCH] refactor config api (#352) Closes #300 Closes #303 --- cmd/mdmctl/apply.go | 2 +- cmd/mdmctl/get.go | 2 +- cmd/mdmctl/mdmcert.go | 37 +++-- cmd/mdmctl/setup.go | 10 ++ cmd/micromdm/serve.go | 43 +++--- dep/depsync/depsync.go | 6 +- platform/api/server/apply/client.go | 12 -- platform/api/server/apply/endpoint.go | 30 ----- platform/api/server/apply/service.go | 89 +----------- platform/api/server/apply/transport_http.go | 24 ---- platform/api/server/list/client.go | 11 -- platform/api/server/list/endpoint.go | 28 ---- platform/api/server/list/service.go | 26 +--- platform/api/server/list/transport_http.go | 19 --- platform/config/apply_deptoken.go | 127 ++++++++++++++++++ platform/config/{ => builtin}/db.go | 15 +-- .../builtin/db_deptoken.go} | 43 +----- platform/config/client.go | 48 ++++--- platform/config/config.go | 2 + platform/config/deptoken.go | 30 +++++ platform/config/get_deptoken.go | 64 +++++++++ ...{endpoints.go => save_push_certificate.go} | 40 ++++-- platform/config/server.go | 54 ++++++++ platform/config/service.go | 25 ++-- platform/config/transport_http.go | 90 ------------- 25 files changed, 420 insertions(+), 457 deletions(-) create mode 100644 platform/config/apply_deptoken.go rename platform/config/{ => builtin}/db.go (87%) rename platform/{deptoken/deptopken.go => config/builtin/db_deptoken.go} (68%) create mode 100644 platform/config/deptoken.go create mode 100644 platform/config/get_deptoken.go rename platform/config/{endpoints.go => save_push_certificate.go} (50%) create mode 100644 platform/config/server.go delete mode 100644 platform/config/transport_http.go diff --git a/cmd/mdmctl/apply.go b/cmd/mdmctl/apply.go index 2391d9d8..c3805b6f 100644 --- a/cmd/mdmctl/apply.go +++ b/cmd/mdmctl/apply.go @@ -184,7 +184,7 @@ func (cmd *applyCommand) applyDEPTokens(args []string) error { return err } ctx := context.Background() - err = cmd.applysvc.ApplyDEPToken(ctx, p7mBytes) + err = cmd.configsvc.ApplyDEPToken(ctx, p7mBytes) if err != nil { return err } diff --git a/cmd/mdmctl/get.go b/cmd/mdmctl/get.go index 9e2eae0c..e05e326d 100644 --- a/cmd/mdmctl/get.go +++ b/cmd/mdmctl/get.go @@ -162,7 +162,7 @@ func (cmd *getCommand) getDepTokens(args []string) error { w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0) fmt.Fprintf(w, "ConsumerKey\tAccessTokenExpiry\n") ctx := context.Background() - tokens, certBytes, err := cmd.list.GetDEPTokens(ctx) + tokens, certBytes, err := cmd.configsvc.GetDEPTokens(ctx) if err != nil { return err } diff --git a/cmd/mdmctl/mdmcert.go b/cmd/mdmctl/mdmcert.go index c1c49df9..919a6dc1 100644 --- a/cmd/mdmctl/mdmcert.go +++ b/cmd/mdmctl/mdmcert.go @@ -13,16 +13,26 @@ import ( "strings" "github.com/go-kit/kit/log" - httptransport "github.com/go-kit/kit/transport/http" "github.com/pkg/errors" "golang.org/x/crypto/pkcs12" "github.com/micromdm/micromdm/pkg/crypto" "github.com/micromdm/micromdm/pkg/crypto/mdmcertutil" - "github.com/micromdm/micromdm/platform/config" ) -type mdmcertCommand struct{} +type mdmcertCommand struct { + *remoteServices +} + +func (cmd *mdmcertCommand) setup() error { + logger := log.NewLogfmtLogger(os.Stderr) + remote, err := setupClient(logger) + if err != nil { + return err + } + cmd.remoteServices = remote + return nil +} func (cmd *mdmcertCommand) Usage() error { const usageText = ` @@ -62,6 +72,10 @@ func (cmd *mdmcertCommand) Run(args []string) error { os.Exit(1) } + if err := cmd.setup(); err != nil { + return err + } + var run func([]string) error switch strings.ToLower(args[0]) { case "vendor": @@ -197,27 +211,12 @@ func (cmd *mdmcertCommand) runUpload(args []string) error { return err } - cfg, err := LoadServerConfig() - if err != nil { - return errors.Wrap(err, "load mdmctl client config") - } - logger := log.NewLogfmtLogger(os.Stderr) - configsvc, err := config.NewClient( - cfg.ServerURL, - logger, - cfg.APIToken, - httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify)), - ) - if err != nil { - return errors.Wrap(err, "create config service from mdmctl config") - } - cert, key, err := loadPushCerts(*flCertPath, *flKeyPath, *flKeyPass) if err != nil { return errors.Wrap(err, "load push certificate") } - if err := configsvc.SavePushCertificate(context.Background(), cert, key); err != nil { + if err := cmd.configsvc.SavePushCertificate(context.Background(), cert, key); err != nil { return errors.Wrap(err, "upload push certificate and key to server") } diff --git a/cmd/mdmctl/setup.go b/cmd/mdmctl/setup.go index 85caff4d..69b9dc51 100644 --- a/cmd/mdmctl/setup.go +++ b/cmd/mdmctl/setup.go @@ -7,6 +7,7 @@ import ( "github.com/micromdm/micromdm/platform/api/server/apply" "github.com/micromdm/micromdm/platform/api/server/list" "github.com/micromdm/micromdm/platform/blueprint" + "github.com/micromdm/micromdm/platform/config" "github.com/micromdm/micromdm/platform/profile" "github.com/micromdm/micromdm/platform/remove" "github.com/micromdm/micromdm/platform/user" @@ -17,6 +18,7 @@ type remoteServices struct { blueprintsvc blueprint.Service blocksvc remove.Service usersvc user.Service + configsvc config.Service applysvc apply.Service list list.Service } @@ -55,6 +57,13 @@ func setupClient(logger log.Logger) (*remoteServices, error) { return nil, err } + configsvc, err := config.NewHTTPClient( + cfg.ServerURL, cfg.APIToken, logger, + httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify))) + if err != nil { + return nil, err + } + applysvc, err := apply.NewClient( cfg.ServerURL, logger, cfg.APIToken, httptransport.SetClient(skipVerifyHTTPClient(cfg.SkipVerify))) @@ -74,6 +83,7 @@ func setupClient(logger log.Logger) (*remoteServices, error) { blueprintsvc: blueprintsvc, blocksvc: blocksvc, usersvc: usersvc, + configsvc: configsvc, applysvc: applysvc, list: listsvc, }, nil diff --git a/cmd/micromdm/serve.go b/cmd/micromdm/serve.go index c243eff6..5920a750 100644 --- a/cmd/micromdm/serve.go +++ b/cmd/micromdm/serve.go @@ -51,7 +51,7 @@ import ( blueprintbuiltin "github.com/micromdm/micromdm/platform/blueprint/builtin" "github.com/micromdm/micromdm/platform/command" "github.com/micromdm/micromdm/platform/config" - "github.com/micromdm/micromdm/platform/deptoken" + configbuiltin "github.com/micromdm/micromdm/platform/config/builtin" "github.com/micromdm/micromdm/platform/device" "github.com/micromdm/micromdm/platform/profile" profilebuiltin "github.com/micromdm/micromdm/platform/profile/builtin" @@ -202,19 +202,6 @@ func serve(args []string) error { ctx := context.Background() httpLogger := log.With(logger, "transport", "http") - var configHandlers config.HTTPHandlers - { - pushCertEndpoint := config.MakeSavePushCertificateEndpoint(sm.configService) - configEndpoints := config.Endpoints{ - SavePushCertificateEndpoint: pushCertEndpoint, - } - configOpts := []httptransport.ServerOption{ - httptransport.ServerErrorLogger(httpLogger), - httptransport.ServerErrorEncoder(checkin.EncodeError), - } - configHandlers = config.MakeHTTPHandlers(ctx, configEndpoints, configOpts...) - } - var checkinHandlers checkin.HTTPHandlers { e := checkin.Endpoints{ @@ -269,7 +256,6 @@ func serve(args []string) error { if err != nil { stdlog.Fatalf("creating DEP client: %s\n", err) } - tokenDB := &deptoken.DB{DB: sm.db, Publisher: sm.pubclient} appDB := &appstore.Repo{Path: *flRepoPath} var profilesvc profile.Service @@ -294,12 +280,18 @@ func serve(args []string) error { userEndpoints := user.MakeServerEndpoints(usersvc) + var configsvc config.Service + { + configsvc = config.New(sm.configDB) + } + + configEndpoints := config.MakeServerEndpoints(configsvc) + var listsvc list.Service { l := &list.ListService{ DEPClient: dc, Devices: devDB, - Tokens: tokenDB, Apps: appDB, } listsvc = l @@ -315,7 +307,6 @@ func serve(args []string) error { } listEndpoints := list.Endpoints{ ListDevicesEndpoint: listDevicesEndpoint, - GetDEPTokensEndpoint: list.MakeGetDEPTokensEndpoint(listsvc), GetDEPAccountInfoEndpoint: list.MakeGetDEPAccountInfoEndpoint(listsvc), GetDEPProfileEndpoint: list.MakeGetDEPProfileEndpoint(listsvc), GetDEPDeviceEndpoint: list.MakeGetDEPDeviceDetailsEndpoint(listsvc), @@ -326,7 +317,6 @@ func serve(args []string) error { { l := &apply.ApplyService{ DEPClient: dc, - Tokens: tokenDB, Apps: appDB, } applysvc = l @@ -346,7 +336,6 @@ func serve(args []string) error { } applyEndpoints := apply.Endpoints{ - ApplyDEPTokensEndpoint: apply.MakeApplyDEPTokensEndpoint(applysvc), DefineDEPProfileEndpoint: defineDEPProfileEndpoint, AppUploadEndpoint: appUploadEndpoint, } @@ -375,6 +364,7 @@ func serve(args []string) error { blueprintsHandler := blueprint.MakeHTTPHandler(blueprintEndpoints, logger) blockhandler := block.MakeHTTPHandler(blockEndpoints, logger) userHandler := user.MakeHTTPHandler(userEndpoints, logger) + configHandler := config.MakeHTTPHandler(configEndpoints, logger) // API commands. Only handled if the user provides an api key. if *flAPIKey != "" { @@ -383,18 +373,18 @@ func serve(args []string) error { r.Handle("/v1/users", apiAuthMiddleware(*flAPIKey, userHandler)) r.Handle("/v1/devices/{udid}/block", apiAuthMiddleware(*flAPIKey, blockhandler)) r.Handle("/v1/devices/{udid}/unblock", apiAuthMiddleware(*flAPIKey, blockhandler)) + r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, configHandler)) + r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, configHandler)) + r.Handle("/v1/config/certificate", apiAuthMiddleware(*flAPIKey, configHandler)) r.Handle("/push/{udid}", apiAuthMiddleware(*flAPIKey, pushHandlers.PushHandler)) r.Handle("/v1/commands", apiAuthMiddleware(*flAPIKey, commandHandlers.NewCommandHandler)).Methods("POST") r.Handle("/v1/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListDevicesHandler)).Methods("GET") - r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPTokensHandler)).Methods("GET") - r.Handle("/v1/dep-tokens", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DEPTokensHandler)).Methods("PUT") r.Handle("/v1/dep/devices", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPDeviceDetailsHandler)).Methods("GET") r.Handle("/v1/dep/account", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPAccountInfoHandler)).Methods("GET") r.Handle("/v1/dep/profiles", apiAuthMiddleware(*flAPIKey, listAPIHandlers.GetDEPProfileHandler)).Methods("GET") r.Handle("/v1/dep/profiles", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.DefineDEPProfileHandler)).Methods("POST") r.Handle("/v1/apps", apiAuthMiddleware(*flAPIKey, applyAPIHandlers.AppUploadHandler)).Methods("POST") r.Handle("/v1/apps", apiAuthMiddleware(*flAPIKey, listAPIHandlers.ListAppsHandler)).Methods("GET") - r.Handle("/v1/config/certificate", apiAuthMiddleware(*flAPIKey, configHandlers.SavePushCertificateHandler)).Methods("PUT") } if *flRepoPath != "" { @@ -490,7 +480,7 @@ type server struct { tlsCertPath string scepDepot *boltdepot.Depot profileDB profile.Store - configDB *config.DB + configDB config.Store removeDB block.Store CommandWebhookURL string @@ -678,13 +668,13 @@ func (c *server) setupConfigStore() { if c.err != nil { return } - db, err := config.NewDB(c.db, c.pubclient) + db, err := configbuiltin.NewDB(c.db, c.pubclient) if err != nil { c.err = err return } c.configDB = db - c.configService = config.NewService(db) + c.configService = config.New(db) } @@ -781,9 +771,8 @@ func (c *server) depClient() (dep.Client, error) { depsim := c.depsim var conf *dep.Config - tokenDB := &deptoken.DB{DB: c.db} // try getting the oauth config from bolt - tokens, err := tokenDB.DEPTokens() + tokens, err := c.configDB.DEPTokens() if err != nil { return nil, err } diff --git a/dep/depsync/depsync.go b/dep/depsync/depsync.go index f15bc1fd..6f9794f4 100644 --- a/dep/depsync/depsync.go +++ b/dep/depsync/depsync.go @@ -13,7 +13,7 @@ import ( "github.com/micromdm/dep" "github.com/pkg/errors" - "github.com/micromdm/micromdm/platform/deptoken" + conf "github.com/micromdm/micromdm/platform/config" "github.com/micromdm/micromdm/platform/pubsub" ) @@ -105,7 +105,7 @@ func New(pub pubsub.PublishSubscriber, db *bolt.DB, opts ...Option) (Syncer, err } func (w *watcher) updateClient(pubsub pubsub.Subscriber) error { - tokenAdded, err := pubsub.Subscribe(context.TODO(), "token-events", deptoken.DEPTokenTopic) + tokenAdded, err := pubsub.Subscribe(context.TODO(), "token-events", conf.DEPTokenTopic) if err != nil { return err } @@ -114,7 +114,7 @@ func (w *watcher) updateClient(pubsub pubsub.Subscriber) error { for { select { case event := <-tokenAdded: - var token deptoken.DEPToken + var token conf.DEPToken if err := json.Unmarshal(event.Message, &token); err != nil { log.Printf("unmarshalling tokenAdded to token: %s\n", err) continue diff --git a/platform/api/server/apply/client.go b/platform/api/server/apply/client.go index 8bb1b9de..49e325e3 100644 --- a/platform/api/server/apply/client.go +++ b/platform/api/server/apply/client.go @@ -16,17 +16,6 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra return nil, err } - var applyDEPTokensEndpoint endpoint.Endpoint - { - applyDEPTokensEndpoint = httptransport.NewClient( - "PUT", - copyURL(u, "/v1/dep-tokens"), - encodeRequestWithToken(token, EncodeHTTPGenericRequest), - DecodeDEPTokensResponse, - opts..., - ).Endpoint() - } - var defineDEPProfileEndpoint endpoint.Endpoint { defineDEPProfileEndpoint = httptransport.NewClient( @@ -50,7 +39,6 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra } return Endpoints{ - ApplyDEPTokensEndpoint: applyDEPTokensEndpoint, DefineDEPProfileEndpoint: defineDEPProfileEndpoint, AppUploadEndpoint: uploadAppEndpoint, }, nil diff --git a/platform/api/server/apply/endpoint.go b/platform/api/server/apply/endpoint.go index 11ba1766..945a8504 100644 --- a/platform/api/server/apply/endpoint.go +++ b/platform/api/server/apply/endpoint.go @@ -9,7 +9,6 @@ import ( ) type Endpoints struct { - ApplyDEPTokensEndpoint endpoint.Endpoint DefineDEPProfileEndpoint endpoint.Endpoint AppUploadEndpoint endpoint.Endpoint } @@ -38,25 +37,6 @@ func (e Endpoints) DefineDEPProfile(ctx context.Context, p *dep.Profile) (*dep.P return response.ProfileResponse, response.Err } -func (e Endpoints) ApplyDEPToken(ctx context.Context, P7MContent []byte) error { - req := depTokensRequest{P7MContent: P7MContent} - resp, err := e.ApplyDEPTokensEndpoint(ctx, req) - if err != nil { - return err - } - return resp.(depTokensResponse).Err -} - -func MakeApplyDEPTokensEndpoint(svc Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (response interface{}, err error) { - req := request.(depTokensRequest) - err = svc.ApplyDEPToken(ctx, req.P7MContent) - return depTokensResponse{ - Err: err, - }, nil - } -} - func MakeDefineDEPProfile(svc Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { req := request.(depProfileRequest) @@ -92,16 +72,6 @@ type appUploadResponse struct { func (r appUploadResponse) error() error { return r.Err } -type depTokensRequest struct { - P7MContent []byte `json:"p7m_content"` -} - -type depTokensResponse struct { - Err error `json:"err,omitempty"` -} - -func (r depTokensResponse) error() error { return r.Err } - type depProfileRequest struct{ *dep.Profile } type depProfileResponse struct { *dep.ProfileResponse diff --git a/platform/api/server/apply/service.go b/platform/api/server/apply/service.go index 81b7a1b9..1c7beb6f 100644 --- a/platform/api/server/apply/service.go +++ b/platform/api/server/apply/service.go @@ -2,26 +2,19 @@ package apply import ( "context" - "log" - "sync" - - "bufio" - "bytes" - "encoding/base64" "encoding/json" "io" - "net/textproto" + "log" + "sync" - "github.com/fullsailor/pkcs7" "github.com/micromdm/dep" "github.com/micromdm/micromdm/platform/appstore" - "github.com/micromdm/micromdm/platform/deptoken" + "github.com/micromdm/micromdm/platform/config" "github.com/micromdm/micromdm/platform/pubsub" ) type Service interface { - ApplyDEPToken(ctx context.Context, P7MContent []byte) error UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error DEPService } @@ -30,8 +23,7 @@ type ApplyService struct { mtx sync.RWMutex DEPClient dep.Client - Tokens *deptoken.DB - Apps appstore.AppStore + Apps appstore.AppStore } func (svc *ApplyService) UploadApp(ctx context.Context, manifestName string, manifest io.Reader, pkgName string, pkg io.Reader) error { @@ -51,7 +43,7 @@ func (svc *ApplyService) UploadApp(ctx context.Context, manifestName string, man } func (svc *ApplyService) WatchTokenUpdates(pubsub pubsub.Subscriber) error { - tokenAdded, err := pubsub.Subscribe(context.TODO(), "apply-token-events", deptoken.DEPTokenTopic) + tokenAdded, err := pubsub.Subscribe(context.TODO(), "apply-token-events", config.DEPTokenTopic) if err != nil { return err } @@ -60,7 +52,7 @@ func (svc *ApplyService) WatchTokenUpdates(pubsub pubsub.Subscriber) error { for { select { case event := <-tokenAdded: - var token deptoken.DEPToken + var token config.DEPToken if err := json.Unmarshal(event.Message, &token); err != nil { log.Printf("unmarshalling tokenAdded to token: %s\n", err) continue @@ -81,72 +73,3 @@ func (svc *ApplyService) WatchTokenUpdates(pubsub pubsub.Subscriber) error { return nil } - -// unwrapSMIME removes the S/MIME-like wrapper around raw CMS/PKCS7 data -func unwrapSMIME(smime []byte) ([]byte, error) { - tr := textproto.NewReader(bufio.NewReader(bytes.NewReader(smime))) - if _, err := tr.ReadMIMEHeader(); err != nil { - return nil, err - } - dec := base64.NewDecoder(base64.StdEncoding, tr.DotReader()) - buf := new(bytes.Buffer) - io.Copy(buf, dec) - return buf.Bytes(), nil -} - -// unwrapTokenJSON removes the MIME-like headers and text surrounding the DEP token JSON -func unwrapTokenJSON(wrapped []byte) ([]byte, error) { - tr := textproto.NewReader(bufio.NewReader(bytes.NewReader(wrapped))) - if _, err := tr.ReadMIMEHeader(); err != nil { - return nil, err - } - tokenJSON := new(bytes.Buffer) - for { - line, err := tr.ReadLineBytes() - if err != nil && err == io.EOF { - break - } else if err != nil { - return nil, err - } - line = bytes.Trim(line, "-----BEGIN MESSAGE-----") - line = bytes.Trim(line, "-----END MESSAGE-----") - if _, err := tokenJSON.Write(line); err != nil { - return nil, err - } - } - return tokenJSON.Bytes(), nil -} - -func (svc *ApplyService) ApplyDEPToken(ctx context.Context, P7MContent []byte) error { - unwrapped, err := unwrapSMIME(P7MContent) - if err != nil { - return err - } - key, cert, err := svc.Tokens.DEPKeypair() - if err != nil { - return err - } - p7, err := pkcs7.Parse(unwrapped) - if err != nil { - return err - } - decrypted, err := p7.Decrypt(cert, key) - if err != nil { - return err - } - tokenJSON, err := unwrapTokenJSON(decrypted) - if err != nil { - return err - } - var depToken deptoken.DEPToken - err = json.Unmarshal(tokenJSON, &depToken) - if err != nil { - return err - } - err = svc.Tokens.AddToken(depToken.ConsumerKey, tokenJSON) - if err != nil { - return err - } - log.Println("stored DEP token with ck", depToken.ConsumerKey) - return nil -} diff --git a/platform/api/server/apply/transport_http.go b/platform/api/server/apply/transport_http.go index 61e0e105..57ad2639 100644 --- a/platform/api/server/apply/transport_http.go +++ b/platform/api/server/apply/transport_http.go @@ -14,19 +14,12 @@ import ( ) type HTTPHandlers struct { - DEPTokensHandler http.Handler DefineDEPProfileHandler http.Handler AppUploadHandler http.Handler } func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers { h := HTTPHandlers{ - DEPTokensHandler: httptransport.NewServer( - endpoints.ApplyDEPTokensEndpoint, - decodeDEPTokensRequest, - encodeResponse, - opts..., - ), DefineDEPProfileHandler: httptransport.NewServer( endpoints.DefineDEPProfileEndpoint, decodeDEPProfileRequest, @@ -43,14 +36,6 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran return h } -func decodeDEPTokensRequest(ctx context.Context, r *http.Request) (interface{}, error) { - var req depTokensRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, err - } - return req, nil -} - func decodeDEPProfileRequest(ctx context.Context, r *http.Request) (interface{}, error) { var req depProfileRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -162,15 +147,6 @@ func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interf return nil } -func DecodeDEPTokensResponse(_ context.Context, r *http.Response) (interface{}, error) { - if r.StatusCode != http.StatusOK { - return nil, errorDecoder(r) - } - var resp depTokensResponse - err := json.NewDecoder(r.Body).Decode(&resp) - return resp, err -} - func DecodeDEPProfileResponse(_ context.Context, r *http.Response) (interface{}, error) { if r.StatusCode != http.StatusOK { return nil, errorDecoder(r) diff --git a/platform/api/server/list/client.go b/platform/api/server/list/client.go index 96a0080e..b4f479bf 100644 --- a/platform/api/server/list/client.go +++ b/platform/api/server/list/client.go @@ -26,16 +26,6 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra opts..., ).Endpoint() } - var getDEPTokensEndpoint endpoint.Endpoint - { - getDEPTokensEndpoint = httptransport.NewClient( - "GET", - copyURL(u, "/v1/dep-tokens"), - encodeRequestWithToken(token, EncodeHTTPGenericRequest), - DecodeGetDEPTokensResponse, - opts..., - ).Endpoint() - } var getDEPAccountInfoEndpoint endpoint.Endpoint { @@ -83,7 +73,6 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra return Endpoints{ ListDevicesEndpoint: listDevicesEndpoint, - GetDEPTokensEndpoint: getDEPTokensEndpoint, GetDEPAccountInfoEndpoint: getDEPAccountInfoEndpoint, GetDEPDeviceEndpoint: getDEPDeviceDetailsEndpoint, GetDEPProfileEndpoint: getDEPProfilesEndpoint, diff --git a/platform/api/server/list/endpoint.go b/platform/api/server/list/endpoint.go index b3d02dbe..f61ee3dc 100644 --- a/platform/api/server/list/endpoint.go +++ b/platform/api/server/list/endpoint.go @@ -6,13 +6,10 @@ import ( "github.com/go-kit/kit/endpoint" "github.com/micromdm/dep" - - "github.com/micromdm/micromdm/platform/deptoken" ) type Endpoints struct { ListDevicesEndpoint endpoint.Endpoint - GetDEPTokensEndpoint endpoint.Endpoint GetDEPAccountInfoEndpoint endpoint.Endpoint GetDEPDeviceEndpoint endpoint.Endpoint GetDEPProfileEndpoint endpoint.Endpoint @@ -37,14 +34,6 @@ func (e Endpoints) ListApplications(ctx context.Context, opts ListAppsOption) ([ return response.(appListResponse).Apps, response.(appListResponse).Err } -func (e Endpoints) GetDEPTokens(ctx context.Context) ([]deptoken.DEPToken, []byte, error) { - resp, err := e.GetDEPTokensEndpoint(ctx, nil) - if err != nil { - return nil, nil, err - } - return resp.(depTokenResponse).DEPTokens, resp.(depTokenResponse).DEPPubKey, nil -} - func (e Endpoints) GetDEPAccountInfo(ctx context.Context) (*dep.Account, error) { request := depAccountInforequest{} response, err := e.GetDEPAccountInfoEndpoint(ctx, request) @@ -94,17 +83,6 @@ func (e Endpoints) GetDEPProfile(ctx context.Context, uuid string) (*dep.Profile return response.(depProfileResponse).Profile, response.(depProfileResponse).Err } -func MakeGetDEPTokensEndpoint(svc Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (response interface{}, err error) { - tokens, pubkey, err := svc.GetDEPTokens(ctx) - return depTokenResponse{ - DEPTokens: tokens, - DEPPubKey: pubkey, - Err: err, - }, nil - } -} - func MakeGetDEPAccountInfoEndpoint(svc Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { account, err := svc.GetDEPAccountInfo(ctx) @@ -141,12 +119,6 @@ type devicesResponse struct { Err error `json:"err,omitempty"` } -type depTokenResponse struct { - DEPTokens []deptoken.DEPToken `json:"dep_tokens"` - DEPPubKey []byte `json:"public_key"` - Err error `json:"err,omitempty"` -} - type depAccountInforequest struct{} type depAccountInfoResponse struct { *dep.Account diff --git a/platform/api/server/list/service.go b/platform/api/server/list/service.go index 70e64743..975d0668 100644 --- a/platform/api/server/list/service.go +++ b/platform/api/server/list/service.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" "github.com/micromdm/micromdm/platform/appstore" - "github.com/micromdm/micromdm/platform/deptoken" + "github.com/micromdm/micromdm/platform/config" "github.com/micromdm/micromdm/platform/device" "github.com/micromdm/micromdm/platform/pubsub" ) @@ -30,7 +30,6 @@ type ListAppsOption struct { type Service interface { ListDevices(ctx context.Context, opt ListDevicesOption) ([]DeviceDTO, error) - GetDEPTokens(ctx context.Context) ([]deptoken.DEPToken, []byte, error) ListApplications(ctx context.Context, opt ListAppsOption) ([]AppDTO, error) DEPService } @@ -40,7 +39,6 @@ type ListService struct { DEPClient dep.Client Devices *device.DB - Tokens *deptoken.DB Apps appstore.AppStore } @@ -68,7 +66,7 @@ func (svc *ListService) ListApplications(ctx context.Context, opts ListAppsOptio } func (svc *ListService) WatchTokenUpdates(pubsub pubsub.Subscriber) error { - tokenAdded, err := pubsub.Subscribe(context.TODO(), "list-token-events", deptoken.DEPTokenTopic) + tokenAdded, err := pubsub.Subscribe(context.TODO(), "list-token-events", config.DEPTokenTopic) if err != nil { return err } @@ -77,7 +75,7 @@ func (svc *ListService) WatchTokenUpdates(pubsub pubsub.Subscriber) error { for { select { case event := <-tokenAdded: - var token deptoken.DEPToken + var token config.DEPToken if err := json.Unmarshal(event.Message, &token); err != nil { log.Printf("unmarshalling tokenAdded to token: %s\n", err) continue @@ -112,21 +110,3 @@ func (svc *ListService) ListDevices(ctx context.Context, opt ListDevicesOption) } return dto, err } - -func (svc *ListService) GetDEPTokens(ctx context.Context) ([]deptoken.DEPToken, []byte, error) { - _, cert, err := svc.Tokens.DEPKeypair() - if err != nil { - return nil, nil, err - } - var certBytes []byte - if cert != nil { - certBytes = cert.Raw - } - - tokens, err := svc.Tokens.DEPTokens() - if err != nil { - return nil, certBytes, err - } - - return tokens, certBytes, nil -} diff --git a/platform/api/server/list/transport_http.go b/platform/api/server/list/transport_http.go index 1828974c..309057bd 100644 --- a/platform/api/server/list/transport_http.go +++ b/platform/api/server/list/transport_http.go @@ -13,7 +13,6 @@ import ( type HTTPHandlers struct { ListDevicesHandler http.Handler - GetDEPTokensHandler http.Handler GetDEPAccountInfoHandler http.Handler GetDEPProfileHandler http.Handler GetDEPDeviceDetailsHandler http.Handler @@ -28,11 +27,6 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran encodeResponse, opts..., ), - GetDEPTokensHandler: httptransport.NewServer( - endpoints.GetDEPTokensEndpoint, - decodeGetDEPTokensRequest, - encodeResponse, - opts...), GetDEPAccountInfoHandler: httptransport.NewServer( endpoints.GetDEPAccountInfoEndpoint, decodeDepAccountInfoRequest, @@ -61,10 +55,6 @@ func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptran return h } -func decodeGetDEPTokensRequest(ctx context.Context, r *http.Request) (interface{}, error) { - return nil, nil -} - func decodeListDevicesRequest(ctx context.Context, r *http.Request) (interface{}, error) { req := devicesRequest{ Opts: ListDevicesOption{}, @@ -154,15 +144,6 @@ func DecodeDevicesResponse(_ context.Context, r *http.Response) (interface{}, er return resp, err } -func DecodeGetDEPTokensResponse(_ context.Context, r *http.Response) (interface{}, error) { - if r.StatusCode != http.StatusOK { - return nil, errorDecoder(r) - } - var resp depTokenResponse - err := json.NewDecoder(r.Body).Decode(&resp) - return resp, err -} - func DecodeDEPAccountInfoResponse(_ context.Context, r *http.Response) (interface{}, error) { if r.StatusCode != http.StatusOK { return nil, errorDecoder(r) diff --git a/platform/config/apply_deptoken.go b/platform/config/apply_deptoken.go new file mode 100644 index 00000000..92c7e65d --- /dev/null +++ b/platform/config/apply_deptoken.go @@ -0,0 +1,127 @@ +package config + +import ( + "bufio" + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/textproto" + + "github.com/fullsailor/pkcs7" + "github.com/go-kit/kit/endpoint" + "github.com/micromdm/micromdm/pkg/httputil" +) + +func (svc *ConfigService) ApplyDEPToken(ctx context.Context, P7MContent []byte) error { + unwrapped, err := unwrapSMIME(P7MContent) + if err != nil { + return err + } + key, cert, err := svc.store.DEPKeypair() + if err != nil { + return err + } + p7, err := pkcs7.Parse(unwrapped) + if err != nil { + return err + } + decrypted, err := p7.Decrypt(cert, key) + if err != nil { + return err + } + tokenJSON, err := unwrapTokenJSON(decrypted) + if err != nil { + return err + } + var depToken DEPToken + err = json.Unmarshal(tokenJSON, &depToken) + if err != nil { + return err + } + err = svc.store.AddToken(depToken.ConsumerKey, tokenJSON) + if err != nil { + return err + } + fmt.Println("stored DEP token with ck", depToken.ConsumerKey) + return nil +} + +type applyDEPTokenRequest struct { + P7MContent []byte `json:"p7m_content"` +} + +type applyDEPTokenResponse struct { + Err error `json:"err,omitempty"` +} + +func (r applyDEPTokenResponse) Failed() error { return r.Err } + +func decodeApplyDEPTokensRequest(ctx context.Context, r *http.Request) (interface{}, error) { + var req applyDEPTokenRequest + err := httputil.DecodeJSONRequest(r, &req) + return req, err +} + +func decodeApplyDEPTokensResponse(_ context.Context, r *http.Response) (interface{}, error) { + var resp applyDEPTokenResponse + err := httputil.DecodeJSONResponse(r, &resp) + return resp, err +} + +func MakeApplyDEPTokensEndpoint(svc Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(applyDEPTokenRequest) + err = svc.ApplyDEPToken(ctx, req.P7MContent) + return applyDEPTokenResponse{ + Err: err, + }, nil + } +} + +func (e Endpoints) ApplyDEPToken(ctx context.Context, P7MContent []byte) error { + req := applyDEPTokenRequest{P7MContent: P7MContent} + resp, err := e.ApplyDEPTokensEndpoint(ctx, req) + if err != nil { + return err + } + return resp.(applyDEPTokenResponse).Err +} + +// unwrapSMIME removes the S/MIME-like wrapper around raw CMS/PKCS7 data +func unwrapSMIME(smime []byte) ([]byte, error) { + tr := textproto.NewReader(bufio.NewReader(bytes.NewReader(smime))) + if _, err := tr.ReadMIMEHeader(); err != nil { + return nil, err + } + dec := base64.NewDecoder(base64.StdEncoding, tr.DotReader()) + buf := new(bytes.Buffer) + io.Copy(buf, dec) + return buf.Bytes(), nil +} + +// unwrapTokenJSON removes the MIME-like headers and text surrounding the DEP token JSON +func unwrapTokenJSON(wrapped []byte) ([]byte, error) { + tr := textproto.NewReader(bufio.NewReader(bytes.NewReader(wrapped))) + if _, err := tr.ReadMIMEHeader(); err != nil { + return nil, err + } + tokenJSON := new(bytes.Buffer) + for { + line, err := tr.ReadLineBytes() + if err != nil && err == io.EOF { + break + } else if err != nil { + return nil, err + } + line = bytes.Trim(line, "-----BEGIN MESSAGE-----") + line = bytes.Trim(line, "-----END MESSAGE-----") + if _, err := tokenJSON.Write(line); err != nil { + return nil, err + } + } + return tokenJSON.Bytes(), nil +} diff --git a/platform/config/db.go b/platform/config/builtin/db.go similarity index 87% rename from platform/config/db.go rename to platform/config/builtin/db.go index 015a6e25..21f00918 100644 --- a/platform/config/db.go +++ b/platform/config/builtin/db.go @@ -1,5 +1,4 @@ -// Package config provides an internal store for the configuration of the MDM server. -package config +package builtin import ( "context" @@ -12,12 +11,12 @@ import ( "github.com/pkg/errors" "github.com/micromdm/micromdm/pkg/crypto" + "github.com/micromdm/micromdm/platform/config" "github.com/micromdm/micromdm/platform/pubsub" ) const ( ConfigBucket = "mdm.ServerConfig" - ConfigTopic = "mdm.ServerConfigUpdated" ) // DB stores server configuration in BoltDB @@ -44,7 +43,7 @@ func (db *DB) SavePushCertificate(cert, key []byte) error { if bkt == nil { return fmt.Errorf("config: bucket %q not found", ConfigBucket) } - pb, err := MarshalServerConfig(&ServerConfig{ + pb, err := config.MarshalServerConfig(&config.ServerConfig{ PushCertificate: cert, PrivateKey: key, }) @@ -59,21 +58,21 @@ func (db *DB) SavePushCertificate(cert, key []byte) error { return err } - if err := db.Publisher.Publish(context.TODO(), ConfigTopic, []byte("updated")); err != nil { + if err := db.Publisher.Publish(context.TODO(), config.ConfigTopic, []byte("updated")); err != nil { return err } return err } -func (db *DB) serverConfig() (*ServerConfig, error) { - var conf ServerConfig +func (db *DB) serverConfig() (*config.ServerConfig, error) { + var conf config.ServerConfig err := db.View(func(tx *bolt.Tx) error { bkt := tx.Bucket([]byte(ConfigBucket)) data := bkt.Get([]byte("config")) if data == nil { return ¬Found{"ServerConfig", "no config found in boltdb"} } - return UnmarshalServerConfig(data, &conf) + return config.UnmarshalServerConfig(data, &conf) }) return &conf, errors.Wrap(err, "get server config from bolt") } diff --git a/platform/deptoken/deptopken.go b/platform/config/builtin/db_deptoken.go similarity index 68% rename from platform/deptoken/deptopken.go rename to platform/config/builtin/db_deptoken.go index 2d68be29..2aeeacfb 100644 --- a/platform/deptoken/deptopken.go +++ b/platform/config/builtin/db_deptoken.go @@ -1,4 +1,4 @@ -package deptoken +package builtin import ( "bytes" @@ -6,47 +6,16 @@ import ( "crypto/rsa" "crypto/x509" "encoding/json" - "time" "github.com/boltdb/bolt" - "github.com/micromdm/dep" - "github.com/micromdm/micromdm/pkg/crypto" - "github.com/micromdm/micromdm/platform/pubsub" + "github.com/micromdm/micromdm/platform/config" ) const ( depTokenBucket = "mdm.DEPToken" - - DEPTokenTopic = "mdm.TokenAdded" ) -type DB struct { - *bolt.DB - Publisher pubsub.Publisher -} - -type DEPToken struct { - ConsumerKey string `json:"consumer_key"` - ConsumerSecret string `json:"consumer_secret"` - AccessToken string `json:"access_token"` - AccessSecret string `json:"access_secret"` - AccessTokenExpiry time.Time `json:"access_token_expiry"` -} - -// create a DEP client from token. -func (tok DEPToken) Client() (dep.Client, error) { - conf := &dep.Config{ - ConsumerKey: tok.ConsumerKey, - ConsumerSecret: tok.ConsumerSecret, - AccessSecret: tok.AccessSecret, - AccessToken: tok.AccessToken, - } - depServerURL := "https://mdmenrollment.apple.com" - client, err := dep.NewClient(conf, dep.ServerURL(depServerURL)) - return client, err -} - func (db *DB) AddToken(consumerKey string, json []byte) error { err := db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte(depTokenBucket)) @@ -58,14 +27,14 @@ func (db *DB) AddToken(consumerKey string, json []byte) error { if err != nil { return err } - if err := db.Publisher.Publish(context.TODO(), DEPTokenTopic, json); err != nil { + if err := db.Publisher.Publish(context.TODO(), config.DEPTokenTopic, json); err != nil { return err } return nil } -func (db *DB) DEPTokens() ([]DEPToken, error) { - var result []DEPToken +func (db *DB) DEPTokens() ([]config.DEPToken, error) { + var result []config.DEPToken err := db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(depTokenBucket)) if b == nil { @@ -75,7 +44,7 @@ func (db *DB) DEPTokens() ([]DEPToken, error) { prefix := []byte("CK_") for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() { - var depToken DEPToken + var depToken config.DEPToken err := json.Unmarshal(v, &depToken) if err != nil { // TODO: log problematic DEP token, or remove altogether? diff --git a/platform/config/client.go b/platform/config/client.go index bcf1cb9d..3367544e 100644 --- a/platform/config/client.go +++ b/platform/config/client.go @@ -1,16 +1,15 @@ package config import ( - "context" - "net/http" "net/url" "github.com/go-kit/kit/endpoint" "github.com/go-kit/kit/log" httptransport "github.com/go-kit/kit/transport/http" + "github.com/micromdm/micromdm/pkg/httputil" ) -func NewClient(instance string, logger log.Logger, token string, opts ...httptransport.ClientOption) (Service, error) { +func NewHTTPClient(instance, token string, logger log.Logger, opts ...httptransport.ClientOption) (Service, error) { u, err := url.Parse(instance) if err != nil { return nil, err @@ -18,31 +17,40 @@ func NewClient(instance string, logger log.Logger, token string, opts ...httptra var saveEndpoint endpoint.Endpoint { - saveEndpoint = httptransport.NewClient( "PUT", - copyURL(u, "/v1/config/certificate"), - encodeRequestWithToken(token, EncodeHTTPGenericRequest), - DecodeSavePushCertificateResponse, + httputil.CopyURL(u, "/v1/config/certificate"), + httputil.EncodeRequestWithToken(token, httptransport.EncodeJSONRequest), + decodeSavePushCertificateResponse, + opts..., + ).Endpoint() + } + + var applyDEPTokensEndpoint endpoint.Endpoint + { + applyDEPTokensEndpoint = httptransport.NewClient( + "PUT", + httputil.CopyURL(u, "/v1/dep-tokens"), + httputil.EncodeRequestWithToken(token, httptransport.EncodeJSONRequest), + decodeApplyDEPTokensResponse, opts..., ).Endpoint() + } + var getDEPTokensEndpoint endpoint.Endpoint + { + getDEPTokensEndpoint = httptransport.NewClient( + "GET", + httputil.CopyURL(u, "/v1/dep-tokens"), + httputil.EncodeRequestWithToken(token, httptransport.EncodeJSONRequest), + decodeGetDEPTokensResponse, + opts..., + ).Endpoint() } return Endpoints{ SavePushCertificateEndpoint: saveEndpoint, + ApplyDEPTokensEndpoint: applyDEPTokensEndpoint, + GetDEPTokensEndpoint: getDEPTokensEndpoint, }, nil } - -func encodeRequestWithToken(token string, next httptransport.EncodeRequestFunc) httptransport.EncodeRequestFunc { - return func(ctx context.Context, r *http.Request, request interface{}) error { - r.SetBasicAuth("micromdm", token) - return next(ctx, r, request) - } -} - -func copyURL(base *url.URL, path string) *url.URL { - next := *base - next.Path = path - return &next -} diff --git a/platform/config/config.go b/platform/config/config.go index 3175b0e3..8830b5c4 100644 --- a/platform/config/config.go +++ b/platform/config/config.go @@ -7,6 +7,8 @@ import ( "github.com/micromdm/micromdm/platform/config/internal/configproto" ) +const ConfigTopic = "mdm.ServerConfigUpdated" + // ServerConfig holds the configuration of the MDM Server. type ServerConfig struct { PushCertificate []byte diff --git a/platform/config/deptoken.go b/platform/config/deptoken.go new file mode 100644 index 00000000..897b6c88 --- /dev/null +++ b/platform/config/deptoken.go @@ -0,0 +1,30 @@ +package config + +import ( + "time" + + "github.com/micromdm/dep" +) + +const DEPTokenTopic = "mdm.TokenAdded" + +type DEPToken struct { + ConsumerKey string `json:"consumer_key"` + ConsumerSecret string `json:"consumer_secret"` + AccessToken string `json:"access_token"` + AccessSecret string `json:"access_secret"` + AccessTokenExpiry time.Time `json:"access_token_expiry"` +} + +// create a DEP client from token. +func (tok DEPToken) Client() (dep.Client, error) { + conf := &dep.Config{ + ConsumerKey: tok.ConsumerKey, + ConsumerSecret: tok.ConsumerSecret, + AccessSecret: tok.AccessSecret, + AccessToken: tok.AccessToken, + } + depServerURL := "https://mdmenrollment.apple.com" + client, err := dep.NewClient(conf, dep.ServerURL(depServerURL)) + return client, err +} diff --git a/platform/config/get_deptoken.go b/platform/config/get_deptoken.go new file mode 100644 index 00000000..2cb690bb --- /dev/null +++ b/platform/config/get_deptoken.go @@ -0,0 +1,64 @@ +package config + +import ( + "context" + "net/http" + + "github.com/go-kit/kit/endpoint" + "github.com/micromdm/micromdm/pkg/httputil" +) + +func (svc *ConfigService) GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error) { + _, cert, err := svc.store.DEPKeypair() + if err != nil { + return nil, nil, err + } + var certBytes []byte + if cert != nil { + certBytes = cert.Raw + } + + tokens, err := svc.store.DEPTokens() + if err != nil { + return nil, certBytes, err + } + + return tokens, certBytes, nil +} + +type getDEPTokenResponse struct { + DEPTokens []DEPToken `json:"dep_tokens"` + DEPPubKey []byte `json:"public_key"` + Err error `json:"err,omitempty"` +} + +func (r getDEPTokenResponse) Failed() error { return r.Err } + +func decodeGetDEPTokensRequest(ctx context.Context, r *http.Request) (interface{}, error) { + return nil, nil +} + +func decodeGetDEPTokensResponse(_ context.Context, r *http.Response) (interface{}, error) { + var resp getDEPTokenResponse + err := httputil.DecodeJSONResponse(r, &resp) + return resp, err +} + +func MakeGetDEPTokensEndpoint(svc Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + tokens, pubkey, err := svc.GetDEPTokens(ctx) + return getDEPTokenResponse{ + DEPTokens: tokens, + DEPPubKey: pubkey, + Err: err, + }, nil + } +} + +func (e Endpoints) GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error) { + resp, err := e.GetDEPTokensEndpoint(ctx, nil) + if err != nil { + return nil, nil, err + } + return resp.(getDEPTokenResponse).DEPTokens, resp.(getDEPTokenResponse).DEPPubKey, nil +} diff --git a/platform/config/endpoints.go b/platform/config/save_push_certificate.go similarity index 50% rename from platform/config/endpoints.go rename to platform/config/save_push_certificate.go index 25f03909..6e47f5f8 100644 --- a/platform/config/endpoints.go +++ b/platform/config/save_push_certificate.go @@ -2,12 +2,16 @@ package config import ( "context" + "net/http" "github.com/go-kit/kit/endpoint" + "github.com/micromdm/micromdm/pkg/httputil" + "github.com/pkg/errors" ) -type Endpoints struct { - SavePushCertificateEndpoint endpoint.Endpoint +func (svc *ConfigService) SavePushCertificate(ctx context.Context, cert, key []byte) error { + err := svc.store.SavePushCertificate(cert, key) + return errors.Wrap(err, "save push certificate") } type saveRequest struct { @@ -16,10 +20,30 @@ type saveRequest struct { } type saveResponse struct { - Err error + Err error `json:"err,omitempty"` } -func (r saveResponse) error() error { return r.Err } +func (r saveResponse) Failed() error { return r.Err } + +func decodeSavePushCertificateRequest(ctx context.Context, r *http.Request) (interface{}, error) { + var req saveRequest + err := httputil.DecodeJSONRequest(r, &req) + return req, err +} + +func decodeSavePushCertificateResponse(_ context.Context, r *http.Response) (interface{}, error) { + var resp saveResponse + err := httputil.DecodeJSONResponse(r, &resp) + return resp, err +} + +func MakeSavePushCertificateEndpoint(svc Service) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(saveRequest) + err = svc.SavePushCertificate(ctx, req.Cert, req.Key) + return saveResponse{Err: err}, nil + } +} func (e Endpoints) SavePushCertificate(ctx context.Context, cert, key []byte) error { request := saveRequest{ @@ -34,11 +58,3 @@ func (e Endpoints) SavePushCertificate(ctx context.Context, cert, key []byte) er return response.(saveResponse).Err } - -func MakeSavePushCertificateEndpoint(svc Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (response interface{}, err error) { - req := request.(saveRequest) - err = svc.SavePushCertificate(ctx, req.Cert, req.Key) - return saveResponse{Err: err}, nil - } -} diff --git a/platform/config/server.go b/platform/config/server.go new file mode 100644 index 00000000..5c53b14c --- /dev/null +++ b/platform/config/server.go @@ -0,0 +1,54 @@ +package config + +import ( + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/log" + httptransport "github.com/go-kit/kit/transport/http" + "github.com/gorilla/mux" + "github.com/micromdm/micromdm/pkg/httputil" +) + +type Endpoints struct { + ApplyDEPTokensEndpoint endpoint.Endpoint + SavePushCertificateEndpoint endpoint.Endpoint + GetDEPTokensEndpoint endpoint.Endpoint +} + +func MakeServerEndpoints(s Service) Endpoints { + return Endpoints{ + ApplyDEPTokensEndpoint: MakeApplyDEPTokensEndpoint(s), + SavePushCertificateEndpoint: MakeSavePushCertificateEndpoint(s), + GetDEPTokensEndpoint: MakeGetDEPTokensEndpoint(s), + } +} + +func MakeHTTPHandler(e Endpoints, logger log.Logger) *mux.Router { + r, options := httputil.NewRouter(logger) + + // PUT /v1/config/certificate create or replace the MDM Push Certificate + // PUT /v1/dep-tokens create or replace a DEP OAuth token + // GET /v1/dep-tokens get the OAuth Token used for the DEP client + + r.Methods("PUT").Path("/v1/config/certificate").Handler(httptransport.NewServer( + e.SavePushCertificateEndpoint, + decodeSavePushCertificateRequest, + httputil.EncodeJSONResponse, + options..., + )) + + r.Methods("PUT").Path("/v1/dep-tokens").Handler(httptransport.NewServer( + e.ApplyDEPTokensEndpoint, + decodeApplyDEPTokensRequest, + httputil.EncodeJSONResponse, + options..., + )) + + r.Methods("GET").Path("/v1/dep-tokens").Handler(httptransport.NewServer( + e.GetDEPTokensEndpoint, + decodeGetDEPTokensRequest, + httputil.EncodeJSONResponse, + options..., + )) + + return r +} diff --git a/platform/config/service.go b/platform/config/service.go index dca90c47..bf8b902f 100644 --- a/platform/config/service.go +++ b/platform/config/service.go @@ -2,23 +2,30 @@ package config import ( "context" - - "github.com/pkg/errors" + "crypto/rsa" + "crypto/tls" + "crypto/x509" ) type Service interface { SavePushCertificate(ctx context.Context, cert, key []byte) error + ApplyDEPToken(ctx context.Context, P7MContent []byte) error + GetDEPTokens(ctx context.Context) ([]DEPToken, []byte, error) } -type ConfigService struct { - store *DB +type Store interface { + SavePushCertificate(cert, key []byte) error + PushCertificate() (*tls.Certificate, error) + PushTopic() (string, error) + DEPKeypair() (key *rsa.PrivateKey, cert *x509.Certificate, err error) + AddToken(consumerKey string, json []byte) error + DEPTokens() ([]DEPToken, error) } -func NewService(db *DB) *ConfigService { - return &ConfigService{store: db} +type ConfigService struct { + store Store } -func (svc *ConfigService) SavePushCertificate(ctx context.Context, cert, key []byte) error { - err := svc.store.SavePushCertificate(cert, key) - return errors.Wrap(err, "save push certificate") +func New(store Store) *ConfigService { + return &ConfigService{store: store} } diff --git a/platform/config/transport_http.go b/platform/config/transport_http.go deleted file mode 100644 index fa408a16..00000000 --- a/platform/config/transport_http.go +++ /dev/null @@ -1,90 +0,0 @@ -package config - -import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "net/http" - - httptransport "github.com/go-kit/kit/transport/http" - "github.com/pkg/errors" -) - -type HTTPHandlers struct { - SavePushCertificateHandler http.Handler -} - -func MakeHTTPHandlers(ctx context.Context, endpoints Endpoints, opts ...httptransport.ServerOption) HTTPHandlers { - h := HTTPHandlers{ - SavePushCertificateHandler: httptransport.NewServer( - endpoints.SavePushCertificateEndpoint, - decodeSavePushCertificateRequest, - encodeResponse, - opts..., - ), - } - return h -} - -func decodeSavePushCertificateRequest(ctx context.Context, r *http.Request) (interface{}, error) { - var req saveRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, err - } - return req, nil -} - -type errorWrapper struct { - Error string `json:"error"` -} - -type errorer interface { - error() error -} - -func errorDecoder(r *http.Response) error { - var w errorWrapper - if err := json.NewDecoder(r.Body).Decode(&w); err != nil { - return err - } - return errors.New(w.Error) -} - -func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error { - if e, ok := response.(errorer); ok && e.error() != nil { - EncodeError(ctx, e.error(), w) - return nil - } - - enc := json.NewEncoder(w) - enc.SetIndent("", " ") - return enc.Encode(response) -} - -func EncodeError(ctx context.Context, err error, w http.ResponseWriter) { - w.WriteHeader(http.StatusInternalServerError) - enc := json.NewEncoder(w) - enc.SetIndent("", " ") - enc.Encode(errorWrapper{Error: err.Error()}) -} - -// EncodeHTTPGenericRequest is a transport/http.EncodeRequestFunc that -// JSON-encodes any request to the request body. Primarily useful in a client. -func EncodeHTTPGenericRequest(_ context.Context, r *http.Request, request interface{}) error { - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(request); err != nil { - return err - } - r.Body = ioutil.NopCloser(&buf) - return nil -} - -func DecodeSavePushCertificateResponse(_ context.Context, r *http.Response) (interface{}, error) { - if r.StatusCode != http.StatusOK { - return nil, errorDecoder(r) - } - var resp saveResponse - err := json.NewDecoder(r.Body).Decode(&resp) - return resp, err -}