Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MySQL storage and tests #5

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ jobs:

- name: Build
run: go build -v ./...

- name: Start test docker containers
if: matrix.platform == 'ubuntu-latest'
run: |
docker-compose -f storage/mysql/docker-compose-test.yml up &

- name: Set
if: matrix.platform == 'ubuntu-latest'
run: echo "NANODEP_MYSQL_STORAGE_TEST=1" >> $GITHUB_ENV

- name: Test
run: go test -v -race ./...
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ release: \
test:
go test -v -cover -race ./...

.PHONY: my docker $(DEPTOKENS) $(DEPSERVER) $(DEPSYNCER) clean release test
.PHONY: my docker $(DEPTOKENS) $(DEPSERVER) $(DEPSYNCER) clean release test
29 changes: 29 additions & 0 deletions cli/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Package cli contains shared command-line helpers and utilities.
package cli

import (
"fmt"

_ "github.com/go-sql-driver/mysql"
"github.com/micromdm/nanodep/storage"
"github.com/micromdm/nanodep/storage/file"
"github.com/micromdm/nanodep/storage/mysql"
)

// Storage parses a storage name and dsn to determine which and return a storage backend.
func Storage(storageName, dsn string) (storage.AllStorage, error) {
var store storage.AllStorage
var err error
switch storageName {
case "file":
if dsn == "" {
dsn = "db"
}
store, err = file.New(dsn)
case "mysql":
store, err = mysql.New(mysql.WithDSN(dsn))
default:
return nil, fmt.Errorf("unknown storage: %q", storageName)
}
return store, err
}
8 changes: 6 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package client implements HTTP privitives for talking with and authenticating with the Apple DEP APIs.
// Package client implements HTTP primitives for talking with and authenticating with the Apple DEP APIs.
package client

import (
Expand All @@ -21,7 +21,11 @@ type Config struct {
}

type ConfigRetriever interface {
RetrieveConfig(context.Context, string) (*Config, error)
// RetrieveConfig reads the JSON DEP config of a DEP name.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

//
// Returns (nil, nil) if the DEP name does not exist, or if the config
// for the DEP name does not exist.
RetrieveConfig(ctx context.Context, name string) (*Config, error)
}

// DefaultConfigRetreiver wraps a ConfigRetriever to return a default configuration.
Expand Down
53 changes: 53 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package client

import (
"context"
"testing"
)

func TestDefaultConfigRetreiver(t *testing.T) {
for _, cfg := range []struct {
name string
cfg *Config
expectedCfg *Config
}{
{
name: "nil",
cfg: nil,
expectedCfg: &Config{BaseURL: DefaultBaseURL},
},
{
name: "empty",
cfg: &Config{},
expectedCfg: &Config{BaseURL: DefaultBaseURL},
},
{
name: "existent",
cfg: &Config{BaseURL: "foo"},
expectedCfg: &Config{BaseURL: "foo"},
},
} {
t.Run(cfg.name, func(t *testing.T) {
c := NewDefaultConfigRetreiver(cfgRetriever{cfg: cfg.cfg})
actualCfg, err := c.RetrieveConfig(context.Background(), "foo")
if err != nil {
t.Fatal(err)
}
if actualCfg == nil {
t.Fatal("expected not-nil config")
}
if *actualCfg != *cfg.expectedCfg {
t.Fatalf("unexpected base URL: %+v vs %+v", actualCfg, cfg.expectedCfg)
}
})
}

}

type cfgRetriever struct {
cfg *Config
}

func (c cfgRetriever) RetrieveConfig(context.Context, string) (*Config, error) {
return c.cfg, nil
}
2 changes: 1 addition & 1 deletion client/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func GetName(ctx context.Context) string {
}

type AuthTokensRetriever interface {
RetrieveAuthTokens(context.Context, string) (*OAuth1Tokens, error)
RetrieveAuthTokens(ctx context.Context, name string) (*OAuth1Tokens, error)
}

type SessionStore interface {
Expand Down
42 changes: 0 additions & 42 deletions cmd/cli.go

This file was deleted.

6 changes: 3 additions & 3 deletions cmd/depserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"os"
"time"

"github.com/micromdm/nanodep/cli"
"github.com/micromdm/nanodep/client"
"github.com/micromdm/nanodep/cmd"
dephttp "github.com/micromdm/nanodep/http"
"github.com/micromdm/nanodep/http/api"
"github.com/micromdm/nanodep/log/stdlogfmt"
Expand All @@ -37,7 +37,7 @@ func main() {
flListen = flag.String("listen", ":9001", "HTTP listen address")
flAPIKey = flag.String("api", "", "API key for API endpoints")
flVersion = flag.Bool("version", false, "print version")
flStorage = flag.String("storage", "", "storage backend")
flStorage = flag.String("storage", "file", "storage backend")
flDSN = flag.String("storage-dsn", "", "storage data source name")
)
flag.Parse()
Expand All @@ -55,7 +55,7 @@ func main() {

logger := stdlogfmt.New(stdlog.Default(), *flDebug)

storage, err := cmd.ParseStorage(*flStorage, *flDSN)
storage, err := cli.Storage(*flStorage, *flDSN)
if err != nil {
logger.Info("msg", "creating storage backend", "err", err)
os.Exit(1)
Expand Down
6 changes: 3 additions & 3 deletions cmd/depsyncer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"syscall"
"time"

"github.com/micromdm/nanodep/cmd"
"github.com/micromdm/nanodep/cli"
"github.com/micromdm/nanodep/godep"
"github.com/micromdm/nanodep/log/stdlogfmt"
depsync "github.com/micromdm/nanodep/sync"
Expand All @@ -30,7 +30,7 @@ func main() {
flLimit = flag.Int("limit", 0, "limit fetch and sync calls to this many devices (0 for server default)")
flDebug = flag.Bool("debug", false, "log debug messages")
flADebug = flag.Bool("debug-assigner", false, "additional debug logging of the device assigner")
flStorage = flag.String("storage", "", "storage backend")
flStorage = flag.String("storage", "file", "storage backend")
flDSN = flag.String("storage-dsn", "", "storage data source name")
flWebhook = flag.String("webhook-url", "", "URL to send requests to")
)
Expand All @@ -53,7 +53,7 @@ func main() {

logger := stdlogfmt.New(stdlog.Default(), *flDebug)

storage, err := cmd.ParseStorage(*flStorage, *flDSN)
storage, err := cli.Storage(*flStorage, *flDSN)
if err != nil {
logger.Info("msg", "creating storage backend", "err", err)
os.Exit(1)
Expand Down
9 changes: 9 additions & 0 deletions docs/operations-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ Configure the `file` storage backend. This backend manages DEP authentication an

*Example:* `-storage file -storage-dsn /path/to/my/db`

##### mysql storage backend

* `-storage mysql`

Configures the MySQL storage backend. The `-dsn` flag should be in the [format the SQL driver expects](https://github.com/go-sql-driver/mysql#dsn-data-source-name).
Be sure to create the storage tables with the [schema.sql](../storage/mysql/schema.sql) file. MySQL 8.0.19 or later is required.

*Example:* `-storage mysql -dsn nanodep:nanodep/mydepdb`

#### -version

* print version
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ go 1.17
require go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352

require github.com/gomodule/oauth1 v0.2.0

require github.com/go-sql-driver/mysql v1.6.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gomodule/oauth1 v0.2.0 h1:/nNHAD99yipOEspQFbAnNmwGTZ1UNXiD/+JLxwx79fo=
github.com/gomodule/oauth1 v0.2.0/go.mod h1:4r/a8/3RkhMBxJQWL5qzbOEcaQmNPIkNoI7P8sXeI08=
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak=
Expand Down
2 changes: 1 addition & 1 deletion http/api/assigner.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func RetrieveAssignerProfileHandler(store sync.AssignerProfileRetriever, logger
}

type AssignerProfileStorer interface {
StoreAssignerProfile(context.Context, string, string) error
StoreAssignerProfile(ctx context.Context, name string, profileUUID string) error
}

// StoreAssignerProfileHandler saves the assigner profile UUID for the
Expand Down
2 changes: 1 addition & 1 deletion http/api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func RetrieveConfigHandler(store client.ConfigRetriever, logger log.Logger) http
}

type ConfigStorer interface {
StoreConfig(context.Context, string, *client.Config) error
StoreConfig(ctx context.Context, name string, config *client.Config) error
}

// StoreConfigHandler stores the DEP server config for the DEP
Expand Down
18 changes: 4 additions & 14 deletions http/api/tokenpki.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"io"
"net/http"
"strconv"
Expand All @@ -18,11 +17,11 @@ import (
)

type TokenPKIRetriever interface {
RetrieveTokenPKI(context.Context, string) ([]byte, []byte, error)
RetrieveTokenPKI(ctx context.Context, name string) (pemCert []byte, pemKey []byte, err error)
}

type TokenPKIStorer interface {
StoreTokenPKI(context.Context, string, []byte, []byte) error
StoreTokenPKI(ctx context.Context, name string, pemCert []byte, pemKey []byte) error
}

// PEMRSAPrivateKey returns key as a PEM block.
Expand Down Expand Up @@ -80,7 +79,7 @@ func GetCertTokenPKIHandler(store TokenPKIStorer, logger log.Logger) http.Handle
return
}
pemCert := tokenpki.PEMCertificate(cert.Raw)
err = store.StoreTokenPKI(r.Context(), r.URL.Path, pemCert, PEMRSAPrivateKey(key))
err = store.StoreTokenPKI(r.Context(), r.URL.Path, pemCert, tokenpki.PEMRSAPrivateKey(key))
if err != nil {
logger.Info("msg", "storing token keypair", "err", err)
jsonError(w, err)
Expand All @@ -92,15 +91,6 @@ func GetCertTokenPKIHandler(store TokenPKIStorer, logger log.Logger) http.Handle
}
}

// RSAKeyFromPEM decodes a PEM RSA private key.
func RSAKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(key)
if block.Type != "RSA PRIVATE KEY" {
return nil, errors.New("PEM type is not RSA PRIVATE KEY")
}
return x509.ParsePKCS1PrivateKey(block.Bytes)
}

// DecryptTokenPKIHandler reads the Apple-provided encrypted token ".p7m" file
// from the request body and decrypts it with the keypair generated from
// GetCertTokenPKIHandler.
Expand Down Expand Up @@ -136,7 +126,7 @@ func DecryptTokenPKIHandler(store TokenPKIRetriever, tokenStore AuthTokensStorer
jsonError(w, err)
return
}
key, err := RSAKeyFromPEM(keyBytes)
key, err := tokenpki.RSAKeyFromPEM(keyBytes)
if err != nil {
logger.Info("msg", "decoding retrieved private key", "err", err)
jsonError(w, err)
Expand Down
2 changes: 1 addition & 1 deletion http/api/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

type AuthTokensStorer interface {
StoreAuthTokens(context.Context, string, *client.OAuth1Tokens) error
StoreAuthTokens(ctx context.Context, name string, tokens *client.OAuth1Tokens) error
}

// RetrieveAuthTokensHandler returns the DEP server OAuth1 tokens for the DEP
Expand Down
Loading