From c723c8011839aeb3fd0b060e75a6b5b36ccd3d44 Mon Sep 17 00:00:00 2001 From: Mosen Date: Tue, 19 Jul 2016 21:39:43 +1000 Subject: [PATCH] Add device uuid to certificate, as there will never be a normalised form of the certificate data. Change statments to reflect table change from certificates to devices_certificates Add the CertificateList request type to the connect service's Acknowledge method Add certificates datastore to the management service, and add endpoints and request/responses for retrieving certificates by device uuid to the management endpoujnt. --- certificates/certificate.go | 1 + certificates/datastore.go | 37 ++++++++++++++++++++++++++++++++++--- connect/service.go | 32 ++++++++++++++++++++++++++++---- main.go | 6 +++--- management/service.go | 17 ++++++++++++++++- management/transport.go | 23 +++++++++++++++++++++++ 6 files changed, 105 insertions(+), 11 deletions(-) diff --git a/certificates/certificate.go b/certificates/certificate.go index ebf80f47..d3875209 100644 --- a/certificates/certificate.go +++ b/certificates/certificate.go @@ -6,6 +6,7 @@ import ( type Certificate struct { UUID string `db:"certificate_uuid" json:"uuid"` + DeviceUUID string `db:"device_uuid" json:"device_uuid"` Data x509.Certificate `db:"data" json:"data"` CommonName string `db:"common_name" json:"common_nane"` IsIdentity bool `db:"is_identity" json:"is_identity"` diff --git a/certificates/datastore.go b/certificates/datastore.go index bb8dd9ed..1295b95c 100644 --- a/certificates/datastore.go +++ b/certificates/datastore.go @@ -11,25 +11,38 @@ import ( ) var ( - insertCertificateStmt = `INSERT INTO certificates ( + insertCertificateStmt = `INSERT INTO devices_certificates ( + device_uuid, common_name, data, is_identity - ) VALUES ($1, $2, $3) + ) VALUES ($1, $2, $3, $4) RETURNING certificate_uuid;` selectCertificatesStmt = `SELECT certificate_uuid, + device_uuid common_name, data, is_identity FROM certificates` + + selectCertificatesByDeviceStmt = `SELECT + certificate_uuid, + certificates.device_uuid device_uuid + common_name, + data, + is_identity + FROM certificates + INNER JOIN devices ON certificates.device_uuid = devices.device_uuid + WHERE devices.udid = $1` ) // This Datastore manages a list of certificates assigned to devices. type Datastore interface { New(crt *Certificate) (string, error) Certificates(params ...interface{}) ([]Certificate, error) + GetCertificatesByDeviceUDID(udid string) ([]Certificate, error) } type pgStore struct { @@ -63,7 +76,7 @@ func NewDB(driver, conn string, logger kitlog.Logger) (Datastore, error) { } func (store pgStore) New(c *Certificate) (string, error) { - if err := store.QueryRow(insertCertificateStmt, c.CommonName, "", c.IsIdentity).Scan(&c.UUID); err != nil { + if err := store.QueryRow(insertCertificateStmt, c.DeviceUUID, c.CommonName, "", c.IsIdentity).Scan(&c.UUID); err != nil { return "", err } @@ -81,6 +94,15 @@ func (store pgStore) Certificates(params ...interface{}) ([]Certificate, error) return certificates, nil } +func (store pgStore) GetCertificatesByDeviceUDID(udid string) ([]Certificate, error) { + var certificates []Certificate + err := store.Select(&certificates, selectCertificatesByDeviceStmt, udid) + if err != nil { + return nil, errors.Wrap(err, "pgStore GetCertificatesByDeviceUDID") + } + return certificates, nil +} + // UUID is a filter that can be added as a parameter to narrow down the list of returned results type UUID struct { UUID string @@ -90,6 +112,15 @@ func (p UUID) where() string { return fmt.Sprintf("certificate_uuid = '%s'", p.UUID) } +// Filter by a device uuid +type DeviceUUID struct { + UUID string +} + +func (p DeviceUUID) where() string { + return fmt.Sprintf("device_uuid = '%s'", p.UUID) +} + // whereer is for building args passed into a method which finds resources type whereer interface { where() string diff --git a/connect/service.go b/connect/service.go index 90cb7184..9f92efb9 100644 --- a/connect/service.go +++ b/connect/service.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/micromdm/mdm" apps "github.com/micromdm/micromdm/applications" + "github.com/micromdm/micromdm/certificates" "github.com/micromdm/micromdm/command" "github.com/micromdm/micromdm/device" "github.com/pkg/errors" @@ -21,11 +22,12 @@ type Service interface { } // NewService creates a mdm service -func NewService(devices device.Datastore, apps apps.Datastore, cs command.Service) Service { +func NewService(devices device.Datastore, apps apps.Datastore, certs certificates.Datastore, cs command.Service) Service { return &service{ commands: cs, devices: devices, apps: apps, + certs: certs, } } @@ -33,6 +35,7 @@ type service struct { devices device.Datastore apps apps.Datastore commands command.Service + certs certificates.Datastore } // Acknowledge a response from a device. @@ -46,11 +49,12 @@ func (svc service) Acknowledge(ctx context.Context, req mdm.Response) (int, erro } case "InstalledApplicationList": if err := svc.ackInstalledApplicationList(req); err != nil { - fmt.Printf("Got an error acknowledging InstalledApplicationList: %v\n", err) return 0, err } case "CertificateList": - return 0, nil + if err := svc.ackCertificateList(req); err != nil { + return 0, err + } default: // Need to handle the absence of RequestType in IOS8 devices if req.QueryResponses.UDID != "" { @@ -64,6 +68,12 @@ func (svc service) Acknowledge(ctx context.Context, req mdm.Response) (int, erro return 0, err } } + + if req.CertificateList != nil { + if err := svc.ackCertificateList(req); err != nil { + return 0, err + } + } } total, err := svc.commands.DeleteCommand(req.UDID, req.CommandUUID) @@ -237,10 +247,24 @@ skip: // Acknowledge a response to `CertificateList`. func (svc service) ackCertificateList(req mdm.Response) error { - _, err := svc.devices.GetDeviceByUDID(req.UDID, "device_uuid") + device, err := svc.devices.GetDeviceByUDID(req.UDID, "device_uuid") if err != nil { return errors.Wrap(err, "getting a device record by udid") } + for _, cert := range req.CertificateList { + newCert := certificates.Certificate{ + CommonName: cert.CommonName, + IsIdentity: cert.IsIdentity, + //Data: cert.Data, + DeviceUUID: device.UUID, + } + + _, err := svc.certs.New(&newCert) + if err != nil { + return errors.Wrap(err, "persisting a device certificate") + } + } + return nil } diff --git a/main.go b/main.go index fbb9a544..acb7916a 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/go-kit/kit/log" "github.com/micromdm/dep" "github.com/micromdm/micromdm/applications" + "github.com/micromdm/micromdm/certificates" "github.com/micromdm/micromdm/checkin" "github.com/micromdm/micromdm/command" "github.com/micromdm/micromdm/connect" @@ -200,11 +201,10 @@ func main() { } dc := depClient(logger, *flDEPCK, *flDEPCS, *flDEPAT, *flDEPAS, *flDEPServerURL, *flDEPsim) - mgmtSvc := management.NewService(deviceDB, workflowDB, dc, pushSvc, appsDB) + mgmtSvc := management.NewService(deviceDB, workflowDB, dc, pushSvc, appsDB, certsDB) commandSvc := command.NewService(commandDB) checkinSvc := checkin.NewService(deviceDB, mgmtSvc, commandSvc, enrollmentProfile) - connectSvc := connect.NewService(deviceDB, commandSvc) - connectSvc := connect.NewService(deviceDB, appsDB, commandSvc) + connectSvc := connect.NewService(deviceDB, appsDB, certsDB, commandSvc) enrollSvc, _ := enroll.NewService(*flPushCert, *flPushPass, *flTLSCACert, *flSCEPURL, *flURL) httpLogger := log.NewContext(logger).With("component", "http") diff --git a/management/service.go b/management/service.go index bf72587b..0a929148 100644 --- a/management/service.go +++ b/management/service.go @@ -5,6 +5,7 @@ import ( "github.com/RobotsAndPencils/buford/push" "github.com/micromdm/dep" "github.com/micromdm/micromdm/applications" + "github.com/micromdm/micromdm/certificates" "github.com/micromdm/micromdm/device" "github.com/micromdm/micromdm/workflow" "github.com/pkg/errors" @@ -31,6 +32,9 @@ type Service interface { // Installed Applications InstalledApps(deviceUUID string) ([]applications.Application, error) + // Installed Certificates + Certificates(deviceUUID string) ([]certificates.Certificate, error) + // AssignWorkflow assigns a workflow to a device AssignWorkflow(deviceUUID, workflowUUID string) error @@ -43,13 +47,14 @@ type Service interface { } // NewService creates a management service -func NewService(ds device.Datastore, ws workflow.Datastore, dc dep.Client, ps *push.Service, as applications.Datastore) Service { +func NewService(ds device.Datastore, ws workflow.Datastore, dc dep.Client, ps *push.Service, as applications.Datastore, cs certificates.Datastore) Service { return &service{ devices: ds, depClient: dc, workflows: ws, pushsvc: ps, applications: as, + certificates: cs, } } @@ -59,6 +64,7 @@ type service struct { workflows workflow.Datastore pushsvc *push.Service applications applications.Datastore + certificates certificates.Datastore } func (svc service) Push(deviceUDID string) (string, error) { @@ -173,3 +179,12 @@ func (svc service) InstalledApps(deviceUUID string) ([]applications.Application, return apps, nil } + +func (svc service) Certificates(deviceUUID string) ([]certificates.Certificate, error) { + certs, err := svc.certificates.GetCertificatesByDeviceUDID(deviceUUID) + if err != nil { + return nil, errors.Wrap(err, "management: certificates") + } + + return certs, nil +} diff --git a/management/transport.go b/management/transport.go index 2eae74e1..11ef73b3 100644 --- a/management/transport.go +++ b/management/transport.go @@ -108,6 +108,13 @@ func ServiceHandler(ctx context.Context, svc Service, logger kitlog.Logger) http encodeResponse, opts..., ) + certificatesHandler := kithttp.NewServer( + ctx, + makeCertificatesEndpoint(svc), + decodeCertificatesRequest, + encodeResponse, + opts..., + ) r := mux.NewRouter() @@ -119,6 +126,7 @@ func ServiceHandler(ctx context.Context, svc Service, logger kitlog.Logger) http r.Handle("/management/v1/devices/{uuid}", updateDeviceHandler).Methods("PATCH") r.Handle("/management/v1/devices/{udid}/push", pushHandler).Methods("POST") r.Handle("/management/v1/devices/{uuid}/applications", installedAppsHandler).Methods("GET") + r.Handle("/management/v1/devices/{uuid}/certificates", certificatesHandler).Methods("GET") // profiles r.Handle("/management/v1/profiles", addProfileHandler).Methods("POST") r.Handle("/management/v1/profiles", listProfilesHandler).Methods("GET") @@ -246,6 +254,21 @@ func decodeInstalledAppsRequest(_ context.Context, r *http.Request) (interface{} return request, nil } +func decodeCertificatesRequest(_ context.Context, r *http.Request) (interface{}, error) { + vars := mux.Vars(r) + deviceUUID, ok := vars["uuid"] + if !ok { + return nil, errBadRouting + } + + var request = certificatesRequest{UUID: deviceUUID} + err := json.NewDecoder(r.Body).Decode(&request) + if err == io.EOF { + return nil, errEmptyRequest + } + return request, nil +} + 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)