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:
Victor Vrantchan
2017-11-23 22:07:57 -05:00
committed by GitHub
parent bc34ace413
commit 91c236c8c3
112 changed files with 165 additions and 233 deletions

48
platform/config/client.go Normal file
View 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
View 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
View 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 &notFound{"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)
}

View 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
}
}

View File

@@ -0,0 +1,3 @@
package configproto
//go:generate protoc --go_out=. config.proto

View 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,
}

View File

@@ -0,0 +1,9 @@
syntax = "proto3";
package configproto;
message ServerConfig {
bytes push_certificate = 1;
bytes push_certificate_key = 2;
}

View 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")
}

View 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
}