mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-11 12:15:34 +08:00
organize essential APIs into platform, workflow and pkg folders (#337)
Add more logic to the way code is organized. /pkg -- library code not directly connected to micromdm /mdm -- packages meant for the services devices interract with. The MDM protocol. /dep -- DEP API and related packages. /platform -- Core APIs the server provides. Commands API, Devices API, queue, pubsub etc. /workflow -- Packages/API that build on top of platform. Today that's the webhook package. Depending on what ends up here, the workflow folder might become its own repository.
This commit is contained in:
48
platform/config/client.go
Normal file
48
platform/config/client.go
Normal file
@@ -0,0 +1,48 @@
|
||||
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"
|
||||
)
|
||||
|
||||
func NewClient(instance string, logger log.Logger, token string, opts ...httptransport.ClientOption) (Service, error) {
|
||||
u, err := url.Parse(instance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var saveEndpoint endpoint.Endpoint
|
||||
{
|
||||
|
||||
saveEndpoint = httptransport.NewClient(
|
||||
"PUT",
|
||||
copyURL(u, "/v1/config/certificate"),
|
||||
encodeRequestWithToken(token, EncodeHTTPGenericRequest),
|
||||
DecodeSavePushCertificateResponse,
|
||||
opts...,
|
||||
).Endpoint()
|
||||
|
||||
}
|
||||
|
||||
return Endpoints{
|
||||
SavePushCertificateEndpoint: saveEndpoint,
|
||||
}, 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
|
||||
}
|
||||
33
platform/config/config.go
Normal file
33
platform/config/config.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/micromdm/platform/config/internal/configproto"
|
||||
)
|
||||
|
||||
// ServerConfig holds the configuration of the MDM Server.
|
||||
type ServerConfig struct {
|
||||
PushCertificate []byte
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
func MarshalServerConfig(conf *ServerConfig) ([]byte, error) {
|
||||
pb := configproto.ServerConfig{
|
||||
PushCertificate: conf.PushCertificate,
|
||||
PushCertificateKey: conf.PrivateKey,
|
||||
}
|
||||
data, err := proto.Marshal(&pb)
|
||||
return data, errors.Wrap(err, "marshal server config to proto")
|
||||
}
|
||||
|
||||
func UnmarshalServerConfig(data []byte, conf *ServerConfig) error {
|
||||
var pb configproto.ServerConfig
|
||||
if err := proto.Unmarshal(data, &pb); err != nil {
|
||||
return errors.Wrap(err, "unmarshal server config from proto")
|
||||
}
|
||||
conf.PushCertificate = pb.GetPushCertificate()
|
||||
conf.PrivateKey = pb.GetPushCertificateKey()
|
||||
return nil
|
||||
}
|
||||
133
platform/config/db.go
Normal file
133
platform/config/db.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Package config provides an internal store for the configuration of the MDM server.
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/micromdm/micromdm/pkg/crypto"
|
||||
"github.com/micromdm/micromdm/platform/pubsub"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigBucket = "mdm.ServerConfig"
|
||||
ConfigTopic = "mdm.ServerConfigUpdated"
|
||||
)
|
||||
|
||||
// DB stores server configuration in BoltDB
|
||||
type DB struct {
|
||||
*bolt.DB
|
||||
Publisher pubsub.Publisher
|
||||
}
|
||||
|
||||
func NewDB(db *bolt.DB, pub pubsub.Publisher) (*DB, error) {
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(ConfigBucket))
|
||||
return err
|
||||
})
|
||||
store := &DB{DB: db, Publisher: pub}
|
||||
return store, err
|
||||
}
|
||||
|
||||
func (db *DB) SavePushCertificate(cert, key []byte) error {
|
||||
tx, err := db.DB.Begin(true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "begin transaction to store push certificate in bolt")
|
||||
}
|
||||
bkt := tx.Bucket([]byte(ConfigBucket))
|
||||
if bkt == nil {
|
||||
return fmt.Errorf("config: bucket %q not found", ConfigBucket)
|
||||
}
|
||||
pb, err := MarshalServerConfig(&ServerConfig{
|
||||
PushCertificate: cert,
|
||||
PrivateKey: key,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "save push cert in bolt bucket")
|
||||
}
|
||||
|
||||
if err := bkt.Put([]byte("config"), pb); err != nil {
|
||||
return errors.Wrap(err, "save ServerConfig in bucket")
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db.Publisher.Publish(context.TODO(), ConfigTopic, []byte("updated")); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) serverConfig() (*ServerConfig, error) {
|
||||
var conf 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 &conf, errors.Wrap(err, "get server config from bolt")
|
||||
}
|
||||
|
||||
func (db *DB) PushCertificate() (*tls.Certificate, error) {
|
||||
conf, err := db.serverConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get server config for push cert")
|
||||
}
|
||||
|
||||
// load private key
|
||||
pkeyBlock, _ := pem.Decode(conf.PrivateKey)
|
||||
if pkeyBlock == nil {
|
||||
return nil, errors.New("decode private key for push cert")
|
||||
}
|
||||
|
||||
priv, err := x509.ParsePKCS1PrivateKey(pkeyBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse push certificate key from server config")
|
||||
}
|
||||
|
||||
// load certificate
|
||||
certBlock, _ := pem.Decode(conf.PushCertificate)
|
||||
if certBlock == nil {
|
||||
return nil, errors.New("decode push certificate PEM")
|
||||
}
|
||||
|
||||
pushCert, err := x509.ParseCertificate(certBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "parse push certificate from server config")
|
||||
}
|
||||
|
||||
cert := tls.Certificate{
|
||||
Certificate: [][]byte{pushCert.Raw},
|
||||
PrivateKey: priv,
|
||||
Leaf: pushCert,
|
||||
}
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
func (db *DB) PushTopic() (string, error) {
|
||||
cert, err := db.PushCertificate()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "get push certificate for topic")
|
||||
}
|
||||
topic, err := crypto.TopicFromCert(cert.Leaf)
|
||||
return topic, errors.Wrap(err, "get topic from push certificate")
|
||||
}
|
||||
|
||||
type notFound struct {
|
||||
ResourceType string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *notFound) Error() string {
|
||||
return fmt.Sprintf("not found: %s %s", e.ResourceType, e.Message)
|
||||
}
|
||||
44
platform/config/endpoints.go
Normal file
44
platform/config/endpoints.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
type Endpoints struct {
|
||||
SavePushCertificateEndpoint endpoint.Endpoint
|
||||
}
|
||||
|
||||
type saveRequest struct {
|
||||
Cert []byte `json:"cert"`
|
||||
Key []byte `json:"key"`
|
||||
}
|
||||
|
||||
type saveResponse struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (r saveResponse) error() error { return r.Err }
|
||||
|
||||
func (e Endpoints) SavePushCertificate(ctx context.Context, cert, key []byte) error {
|
||||
request := saveRequest{
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
response, err := e.SavePushCertificateEndpoint(ctx, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
3
platform/config/internal/configproto/config.go
Normal file
3
platform/config/internal/configproto/config.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package configproto
|
||||
|
||||
//go:generate protoc --go_out=. config.proto
|
||||
70
platform/config/internal/configproto/config.pb.go
Normal file
70
platform/config/internal/configproto/config.pb.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: config.proto
|
||||
|
||||
/*
|
||||
Package configproto is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
config.proto
|
||||
|
||||
It has these top-level messages:
|
||||
ServerConfig
|
||||
*/
|
||||
package configproto
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type ServerConfig struct {
|
||||
PushCertificate []byte `protobuf:"bytes,1,opt,name=push_certificate,json=pushCertificate,proto3" json:"push_certificate,omitempty"`
|
||||
PushCertificateKey []byte `protobuf:"bytes,2,opt,name=push_certificate_key,json=pushCertificateKey,proto3" json:"push_certificate_key,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ServerConfig) Reset() { *m = ServerConfig{} }
|
||||
func (m *ServerConfig) String() string { return proto.CompactTextString(m) }
|
||||
func (*ServerConfig) ProtoMessage() {}
|
||||
func (*ServerConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *ServerConfig) GetPushCertificate() []byte {
|
||||
if m != nil {
|
||||
return m.PushCertificate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ServerConfig) GetPushCertificateKey() []byte {
|
||||
if m != nil {
|
||||
return m.PushCertificateKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*ServerConfig)(nil), "configproto.ServerConfig")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("config.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 115 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0xce, 0xcf, 0x4b,
|
||||
0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x86, 0xf0, 0xc0, 0x1c, 0xa5, 0x6c,
|
||||
0x2e, 0x9e, 0xe0, 0xd4, 0xa2, 0xb2, 0xd4, 0x22, 0x67, 0xb0, 0xa0, 0x90, 0x26, 0x97, 0x40, 0x41,
|
||||
0x69, 0x71, 0x46, 0x7c, 0x72, 0x6a, 0x51, 0x49, 0x66, 0x5a, 0x66, 0x72, 0x62, 0x49, 0xaa, 0x04,
|
||||
0xa3, 0x02, 0xa3, 0x06, 0x4f, 0x10, 0x3f, 0x48, 0xdc, 0x19, 0x21, 0x2c, 0x64, 0xc0, 0x25, 0x82,
|
||||
0xae, 0x34, 0x3e, 0x3b, 0xb5, 0x52, 0x82, 0x09, 0xac, 0x5c, 0x08, 0x4d, 0xb9, 0x77, 0x6a, 0x65,
|
||||
0x12, 0x1b, 0xd8, 0x4e, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xea, 0xa7, 0xb0, 0xbc, 0x90,
|
||||
0x00, 0x00, 0x00,
|
||||
}
|
||||
9
platform/config/internal/configproto/config.proto
Normal file
9
platform/config/internal/configproto/config.proto
Normal file
@@ -0,0 +1,9 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package configproto;
|
||||
|
||||
message ServerConfig {
|
||||
bytes push_certificate = 1;
|
||||
bytes push_certificate_key = 2;
|
||||
}
|
||||
|
||||
24
platform/config/service.go
Normal file
24
platform/config/service.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
SavePushCertificate(ctx context.Context, cert, key []byte) error
|
||||
}
|
||||
|
||||
type ConfigService struct {
|
||||
store *DB
|
||||
}
|
||||
|
||||
func NewService(db *DB) *ConfigService {
|
||||
return &ConfigService{store: db}
|
||||
}
|
||||
|
||||
func (svc *ConfigService) SavePushCertificate(ctx context.Context, cert, key []byte) error {
|
||||
err := svc.store.SavePushCertificate(cert, key)
|
||||
return errors.Wrap(err, "save push certificate")
|
||||
}
|
||||
90
platform/config/transport_http.go
Normal file
90
platform/config/transport_http.go
Normal file
@@ -0,0 +1,90 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user