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

pgsql #51

Merged
merged 25 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
911835e
started schema migration from MySQL to PostgreSQL
sheshenia May 26, 2022
2463fcf
changes MySQL -> PostgreSQL prepared statements ? -> $1...
sheshenia May 26, 2022
244f114
continue queries migration MySQL -> PostgreSQL
sheshenia May 27, 2022
f37203b
triggers added to update_at, cli added postgreSQL
sheshenia May 27, 2022
be84b01
migrating queue, need deep analyse ClearQueue of UPDATE query
sheshenia May 27, 2022
283a3e8
solving bugs and testing fails, TODO TestQueue/notnow
sheshenia May 27, 2022
fdef433
tests pass ok
sheshenia May 27, 2022
433fe78
PostgreSQL added
sheshenia May 27, 2022
8f55319
PostgreSQL added fix gofmt
sheshenia May 29, 2022
42a3e2d
Update README.md
sheshenia Jun 3, 2022
2e792de
Update storage/postgresql/schema.sql
sheshenia Jun 3, 2022
657ff26
Update storage/postgresql/schema.sql
sheshenia Jun 3, 2022
9f6a7a9
Update storage/postgresql/postgresql.go
sheshenia Jun 4, 2022
98ea9a5
started
sheshenia Jun 5, 2022
fe23b5b
pgsql
sheshenia Jun 5, 2022
5a1417e
Merge branch 'micromdm:main' into main
sheshenia Jun 6, 2022
f388813
pgsql update fixed. Ok tested on real devices. Without "deleter" func…
sheshenia Jun 6, 2022
362b0a3
mysql deleter functionality
sheshenia Jun 7, 2022
ed36ad1
pgsql deleter functionality. todo tests
sheshenia Jun 7, 2022
9cb344b
pgsql deleter tests and docs
sheshenia Jun 7, 2022
1300373
typos fix
sheshenia Jun 7, 2022
94533a6
conflicts mysql - pgsql solved
sheshenia Jun 21, 2022
e986d1d
Merge remote-tracking branch 'upstream/main'
sheshenia Jun 21, 2022
d2a274c
Update storage/mysql/mysql.go
sheshenia Jul 15, 2022
726b2be
on conflict update time now
sheshenia Jul 15, 2022
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A brief overview of the various command-line switches and HTTP endpoints and API

## Features

- Horizontal scaling: zero/minimal local state. Persistence in storage layers. MySQL backend provided in the box.
- Horizontal scaling: zero/minimal local state. Persistence in storage layers. MySQL and PostgreSQL backends provided in the box.
- Multiple APNs topics: potentially multi-tenant.
- Multi-command targeting: send the same command (or pushes) to multiple enrollments without individually queuing commands.
- Migration endpoint: allow migrating MDM enrollments between storage backends or (supported) MDM servers
Expand Down
32 changes: 32 additions & 0 deletions cmd/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"github.com/micromdm/nanomdm/storage/allmulti"
"github.com/micromdm/nanomdm/storage/file"
"github.com/micromdm/nanomdm/storage/mysql"
"github.com/micromdm/nanomdm/storage/pgsql"

_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
)

type StringAccumulator []string
Expand Down Expand Up @@ -72,6 +74,12 @@ func (s *Storage) Parse(logger log.Logger) (storage.AllStorage, error) {
return nil, err
}
mdmStorage = append(mdmStorage, mysqlStorage)
case "pgsql":
pgsqlStorage, err := pgsqlStorageConfig(dsn, options, logger)
if err != nil {
return nil, err
}
mdmStorage = append(mdmStorage, pgsqlStorage)
default:
return nil, fmt.Errorf("unknown storage: %s", storage)
}
Expand Down Expand Up @@ -134,3 +142,27 @@ func splitOptions(s string) map[string]string {
}
return out
}

func pgsqlStorageConfig(dsn, options string, logger log.Logger) (*pgsql.PgSQLStorage, error) {
logger = logger.With("storage", "pgsql")
opts := []pgsql.Option{
pgsql.WithDSN(dsn),
pgsql.WithLogger(logger),
}
if options != "" {
for k, v := range splitOptions(options) {
switch k {
case "delete":
if v == "1" {
opts = append(opts, pgsql.WithDeleteCommands())
logger.Debug("msg", "deleting commands")
} else if v != "0" {
return nil, fmt.Errorf("invalid value for delete option: %q", v)
}
default:
return nil, fmt.Errorf("invalid option: %q", k)
}
}
}
return pgsql.New(opts...)
}
14 changes: 14 additions & 0 deletions docs/operations-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ Options are specified as a comma-separated list of "key=value" pairs. The mysql

*Example:* `-storage mysql -dsn nanomdm:nanomdm/mymdmdb -storage-options delete=1`

#### pgsql storage backend

* `-storage pgsql`

Configures the PostgreSQL storage backend. The `-dsn` flag should be in the [format the SQL driver expects](https://pkg.go.dev/github.com/lib/pq#pkg-overview). Be sure to create your tables with the [schema.sql](../storage/pgsql/schema.sql) file that corresponds to your NanoMDM version. Also make sure you apply any schema changes for each updated version (i.e. execute the numbered schema change files). PostgreSQL 9.5 or later is required.

*Example:* `-storage pgsql -dsn postgres://postgres:toor@localhost:5432/nanomdm?sslmode=disable`

Options are specified as a comma-separated list of "key=value" pairs. The pgsql backend supports these options:
* `delete=1`, `delete=0`
* This option turns on or off the command and response deleter. It is disabled by default. When enabled (with `delete=1`) command responses, queued commands, and commands themselves will be deleted from the database after enrollments have responded to a command.

*Example:* `-storage pgsql -dsn postgres://postgres:toor@localhost/nanomdm -storage-options delete=1`

#### multi-storage backend

You can configure multiple storage backends to be used simultaneously. Specifying multiple sets of `-storage`, `-dsn`, & `-storage-options` flags will configure the "multi-storage" adapter. The flags must be specified in sets and are related to each other in the order they're specified: for example the first `-storage` flag corresponds to the first `-dsn` flag and so forth.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ require (
github.com/RobotsAndPencils/buford v0.14.0
github.com/go-sql-driver/mysql v1.6.0
github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5
github.com/lib/pq v1.10.6
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5 h1:saaSiB25B1wgaxrshQhurfPKUGJ4It3OxNJUy0rdOjU=
github.com/groob/plist v0.0.0-20220217120414-63fa881b19a5/go.mod h1:itkABA+w2cw7x5nYUS/pLRef6ludkZKOigbROmCTaFw=
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak=
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
2 changes: 1 addition & 1 deletion storage/mysql/mysql.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Pacakge mysql stores and retrieves MDM data from SQL
// Package mysql Package mysql stores and retrieves MDM data from MySQL
Copy link
Member

Choose a reason for hiding this comment

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

Duplicated 'package mysql'?

sheshenia marked this conversation as resolved.
Show resolved Hide resolved
package mysql

import (
Expand Down
36 changes: 36 additions & 0 deletions storage/pgsql/bstoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package pgsql

import (
"github.com/micromdm/nanomdm/mdm"
)

func (s *PgSQLStorage) StoreBootstrapToken(r *mdm.Request, msg *mdm.SetBootstrapToken) error {
_, err := s.db.ExecContext(
r.Context,
`UPDATE devices SET bootstrap_token_b64 = $1, bootstrap_token_at = CURRENT_TIMESTAMP WHERE id = $2;`,
nullEmptyString(msg.BootstrapToken.BootstrapToken.String()),
r.ID,
)
if err != nil {
return err
}
return s.updateLastSeen(r)
}

func (s *PgSQLStorage) RetrieveBootstrapToken(r *mdm.Request, _ *mdm.GetBootstrapToken) (*mdm.BootstrapToken, error) {
var tokenB64 string
err := s.db.QueryRowContext(
r.Context,
`SELECT bootstrap_token_b64 FROM devices WHERE id = $1;`,
r.ID,
).Scan(&tokenB64)
if err != nil {
return nil, err
}
bsToken := new(mdm.BootstrapToken)
err = bsToken.SetTokenString(tokenB64)
if err == nil {
err = s.updateLastSeen(r)
}
return bsToken, err
}
52 changes: 52 additions & 0 deletions storage/pgsql/certauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package pgsql

import (
"context"
"strings"

"github.com/micromdm/nanomdm/mdm"
)

// Executes SQL statements that return a single COUNT(*) of rows.
func (s *PgSQLStorage) queryRowContextRowExists(ctx context.Context, query string, args ...interface{}) (bool, error) {
var ct int
err := s.db.QueryRowContext(ctx, query, args...).Scan(&ct)
return ct > 0, err
}

func (s *PgSQLStorage) EnrollmentHasCertHash(r *mdm.Request, _ string) (bool, error) {
return s.queryRowContextRowExists(
r.Context,
`SELECT COUNT(*) FROM cert_auth_associations WHERE id = $1;`,
r.ID,
)
}

func (s *PgSQLStorage) HasCertHash(r *mdm.Request, hash string) (bool, error) {
return s.queryRowContextRowExists(
r.Context,
`SELECT COUNT(*) FROM cert_auth_associations WHERE sha256 = $1;`,
strings.ToLower(hash),
)
}

func (s *PgSQLStorage) IsCertHashAssociated(r *mdm.Request, hash string) (bool, error) {
return s.queryRowContextRowExists(
r.Context,
`SELECT COUNT(*) FROM cert_auth_associations WHERE id = $1 AND sha256 = $2;`,
r.ID, strings.ToLower(hash),
)
}

// AssociateCertHash "DO NOTHING" on duplicated keys
func (s *PgSQLStorage) AssociateCertHash(r *mdm.Request, hash string) error {
_, err := s.db.ExecContext(
r.Context, `
INSERT INTO cert_auth_associations (id, sha256)
VALUES ($1, $2)
ON CONFLICT ON CONSTRAINT cert_auth_associations_pkey DO NOTHING;`,
Copy link
Member

Choose a reason for hiding this comment

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

Nice! I see you added this from your comment from #50.

Copy link
Member

Choose a reason for hiding this comment

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

On second thought there is one reason why you might not want to DO NOTHING. And that's to update the timestamp. Does the timestamp update on a do nothing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated_at time doesn't update on "do nothing". The solution is
ON CONFLICT ON CONSTRAINT cert_auth_associations_pkey DO UPDATE SET updated_at=now();
Should I make changes?

Copy link
Member

Choose a reason for hiding this comment

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

Sure, go for it!

r.ID,
strings.ToLower(hash),
)
return err
}
61 changes: 61 additions & 0 deletions storage/pgsql/migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package pgsql

import (
"context"

"github.com/micromdm/nanomdm/mdm"
)

func (s *PgSQLStorage) RetrieveMigrationCheckins(ctx context.Context, c chan<- interface{}) error {
// TODO: if a TokenUpdate does not include the latest UnlockToken
// then we should synthesize a TokenUpdate to transfer it over.
deviceRows, err := s.db.QueryContext(
ctx,
`SELECT authenticate, token_update FROM devices;`,
)
if err != nil {
return err
}
defer deviceRows.Close()
for deviceRows.Next() {
var authBytes, tokenBytes []byte
if err := deviceRows.Scan(&authBytes, &tokenBytes); err != nil {
return err
}
for _, msgBytes := range [][]byte{authBytes, tokenBytes} {
msg, err := mdm.DecodeCheckin(msgBytes)
if err != nil {
c <- err
} else {
c <- msg
}
}
}
if err = deviceRows.Err(); err != nil {
return err
}
userRows, err := s.db.QueryContext(
ctx,
`SELECT token_update FROM users;`,
)
if err != nil {
return err
}
defer userRows.Close()
for userRows.Next() {
var msgBytes []byte
if err := userRows.Scan(&msgBytes); err != nil {
return err
}
msg, err := mdm.DecodeCheckin(msgBytes)
if err != nil {
c <- err
} else {
c <- msg
}
}
if err = userRows.Err(); err != nil {
return err
}
return nil
}
Loading