Repository: docker/notary (go)

Issue:
Add user + password configuration for RethinkDB databases
RethinkDB 2.3.0 supports users and permissions to isolate database access

Pull request:
Add separate users to rethinkDB databases with only read/write permissions
Adds a `server` and `signer` user for rethinkdb databases if we're bootstrapping, in parity with the mysql configuration.  This is a new feature in RethinkDB 2.3, and I also bumped gorethink to account for a bugfix on their end.

All non-bootstrapping invocations of notary-server and notary-signer will directly connect to rethink with its own users (`username`) and passwords (`password`) from the `storage` section of the server and signer configs, respectively.

Currently does not include password changing for admin.

Tested with the rethink integration test.

Closes #730 

Patch:
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index ee193b4b..69640075 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -182,23 +182,23 @@
 		},
 		{
 			"ImportPath": "gopkg.in/dancannon/gorethink.v2",
-			"Comment": "v2.0.0",
-			"Rev": "ad28ba0b1cbf0aa5bbae1ea71941564cc08abfe4"
+			"Comment": "v2.0.2",
+			"Rev": "3742792da4bc279ccd6d807f24687009cbeda860"
 		},
 		{
 			"ImportPath": "gopkg.in/dancannon/gorethink.v2/encoding",
-			"Comment": "v2.0.0",
-			"Rev": "ad28ba0b1cbf0aa5bbae1ea71941564cc08abfe4"
+			"Comment": "v2.0.2",
+			"Rev": "3742792da4bc279ccd6d807f24687009cbeda860"
 		},
 		{
 			"ImportPath": "gopkg.in/dancannon/gorethink.v2/ql2",
-			"Comment": "v2.0.0",
-			"Rev": "ad28ba0b1cbf0aa5bbae1ea71941564cc08abfe4"
+			"Comment": "v2.0.2",
+			"Rev": "3742792da4bc279ccd6d807f24687009cbeda860"
 		},
 		{
 			"ImportPath": "gopkg.in/dancannon/gorethink.v2/types",
-			"Comment": "v2.0.0",
-			"Rev": "ad28ba0b1cbf0aa5bbae1ea71941564cc08abfe4"
+			"Comment": "v2.0.2",
+			"Rev": "3742792da4bc279ccd6d807f24687009cbeda860"
 		},
 		{
 			"ImportPath": "github.com/denisenkom/go-mssqldb",
diff --git a/cmd/notary-server/config.go b/cmd/notary-server/config.go
index c875b06a..38857bc1 100644
--- a/cmd/notary-server/config.go
+++ b/cmd/notary-server/config.go
@@ -94,11 +94,15 @@ func getStore(configuration *viper.Viper, hRegister healthRegister) (
 			CertFile: storeConfig.Cert,
 			KeyFile:  storeConfig.Key,
 		}
-		sess, err = rethinkdb.Connection(tlsOpts, storeConfig.Source)
+		if doBootstrap {
+			sess, err = rethinkdb.AdminConnection(tlsOpts, storeConfig.Source)
+		} else {
+			sess, err = rethinkdb.UserConnection(tlsOpts, storeConfig.Source, storeConfig.Username, storeConfig.Password)
+		}
 		if err != nil {
 			return nil, fmt.Errorf("Error starting %s driver: %s", backend, err.Error())
 		}
-		s := storage.NewRethinkDBStorage(storeConfig.DBName, sess)
+		s := storage.NewRethinkDBStorage(storeConfig.DBName, storeConfig.Username, storeConfig.Password, sess)
 		store = *storage.NewTUFMetaStorage(s)
 		hRegister("DB operational", s.CheckHealth, time.Minute)
 	default:
diff --git a/cmd/notary-signer/config.go b/cmd/notary-signer/config.go
index 41c049f5..05b7a106 100644
--- a/cmd/notary-signer/config.go
+++ b/cmd/notary-signer/config.go
@@ -127,11 +127,15 @@ func setUpCryptoservices(configuration *viper.Viper, allowedBackends []string) (
 			CertFile: storeConfig.Cert,
 			KeyFile:  storeConfig.Key,
 		}
-		sess, err = rethinkdb.Connection(tlsOpts, storeConfig.Source)
+		if doBootstrap {
+			sess, err = rethinkdb.AdminConnection(tlsOpts, storeConfig.Source)
+		} else {
+			sess, err = rethinkdb.UserConnection(tlsOpts, storeConfig.Source, storeConfig.Username, storeConfig.Password)
+		}
 		if err != nil {
-			return nil, err
+			return nil, fmt.Errorf("Error starting %s driver: %s", backend, err.Error())
 		}
-		s := keydbstore.NewRethinkDBKeyStore(storeConfig.DBName, passphraseRetriever, defaultAlias, sess)
+		s := keydbstore.NewRethinkDBKeyStore(storeConfig.DBName, storeConfig.Username, storeConfig.Password, passphraseRetriever, defaultAlias, sess)
 		health.RegisterPeriodicFunc("DB operational", s.CheckHealth, time.Minute)
 		keyStore = s
 	case notary.MySQLBackend, notary.SQLiteBackend:
diff --git a/fixtures/server-config.rethink.json b/fixtures/server-config.rethink.json
index 06c3a909..18ddf7cb 100644
--- a/fixtures/server-config.rethink.json
+++ b/fixtures/server-config.rethink.json
@@ -22,6 +22,8 @@
 		"database": "notaryserver",
 		"tls_ca_file": "./rethinkdb/ca.pem",
 		"client_key_file": "./rethinkdb/key.pem",
-		"client_cert_file": "./rethinkdb/cert.pem"
+		"client_cert_file": "./rethinkdb/cert.pem",
+		"username": "server",
+		"password": "serverpass"
 	}
 }
diff --git a/fixtures/signer-config.rethink.json b/fixtures/signer-config.rethink.json
index a36e7e6b..fc0bc0d5 100644
--- a/fixtures/signer-config.rethink.json
+++ b/fixtures/signer-config.rethink.json
@@ -15,6 +15,8 @@
 		"database": "notarysigner",
 		"tls_ca_file": "./rethinkdb/ca.pem",
 		"client_key_file": "./rethinkdb/key.pem",
-		"client_cert_file": "./rethinkdb/cert.pem"
+		"client_cert_file": "./rethinkdb/cert.pem",
+		"username": "signer",
+		"password": "signerpass"
 	}
 }
diff --git a/server/storage/rethinkdb.go b/server/storage/rethinkdb.go
index 964a4eb3..1d240d0c 100644
--- a/server/storage/rethinkdb.go
+++ b/server/storage/rethinkdb.go
@@ -45,15 +45,19 @@ func (r RDBKey) TableName() string {
 
 // RethinkDB implements a MetaStore against the Rethink Database
 type RethinkDB struct {
-	dbName string
-	sess   *gorethink.Session
+	dbName   string
+	sess     *gorethink.Session
+	user     string
+	password string
 }
 
 // NewRethinkDBStorage initializes a RethinkDB object
-func NewRethinkDBStorage(dbName string, sess *gorethink.Session) RethinkDB {
+func NewRethinkDBStorage(dbName, user, password string, sess *gorethink.Session) RethinkDB {
 	return RethinkDB{
-		dbName: dbName,
-		sess:   sess,
+		dbName:   dbName,
+		sess:     sess,
+		user:     user,
+		password: password,
 	}
 }
 
@@ -262,12 +266,15 @@ func (rdb RethinkDB) deleteByTSChecksum(tsChecksum string) error {
 	return nil
 }
 
-// Bootstrap sets up the database and tables
+// Bootstrap sets up the database and tables, also creating the notary server user with appropriate db permission
 func (rdb RethinkDB) Bootstrap() error {
-	return rethinkdb.SetupDB(rdb.sess, rdb.dbName, []rethinkdb.Table{
+	if err := rethinkdb.SetupDB(rdb.sess, rdb.dbName, []rethinkdb.Table{
 		tufFiles,
 		keys,
-	})
+	}); err != nil {
+		return err
+	}
+	return rethinkdb.CreateAndGrantDBUser(rdb.sess, rdb.dbName, rdb.user, rdb.password)
 }
 
 // CheckHealth is currently a noop
diff --git a/signer/keydbstore/rethink_keydbstore.go b/signer/keydbstore/rethink_keydbstore.go
index bc460a83..63730125 100644
--- a/signer/keydbstore/rethink_keydbstore.go
+++ b/signer/keydbstore/rethink_keydbstore.go
@@ -23,6 +23,8 @@ type RethinkDBKeyStore struct {
 	defaultPassAlias string
 	retriever        passphrase.Retriever
 	cachedKeys       map[string]data.PrivateKey
+	user             string
+	password         string
 }
 
 // RDBPrivateKey represents a PrivateKey in the rethink database
@@ -48,7 +50,7 @@ func (g RDBPrivateKey) TableName() string {
 }
 
 // NewRethinkDBKeyStore returns a new RethinkDBKeyStore backed by a RethinkDB database
-func NewRethinkDBKeyStore(dbName string, passphraseRetriever passphrase.Retriever, defaultPassAlias string, rethinkSession *gorethink.Session) *RethinkDBKeyStore {
+func NewRethinkDBKeyStore(dbName, username, password string, passphraseRetriever passphrase.Retriever, defaultPassAlias string, rethinkSession *gorethink.Session) *RethinkDBKeyStore {
 	cachedKeys := make(map[string]data.PrivateKey)
 
 	return &RethinkDBKeyStore{
@@ -58,6 +60,8 @@ func NewRethinkDBKeyStore(dbName string, passphraseRetriever passphrase.Retrieve
 		dbName:           dbName,
 		retriever:        passphraseRetriever,
 		cachedKeys:       cachedKeys,
+		user:             username,
+		password:         password,
 	}
 }
 
@@ -236,11 +240,14 @@ func (rdb RethinkDBKeyStore) ExportKey(keyID string) ([]byte, error) {
 	return nil, errors.New("Exporting from a RethinkDBKeyStore is not supported.")
 }
 
-// Bootstrap sets up the database and tables
+// Bootstrap sets up the database and tables, also creating the notary signer user with appropriate db permission
 func (rdb RethinkDBKeyStore) Bootstrap() error {
-	return rethinkdb.SetupDB(rdb.sess, rdb.dbName, []rethinkdb.Table{
+	if err := rethinkdb.SetupDB(rdb.sess, rdb.dbName, []rethinkdb.Table{
 		privateKeys,
-	})
+	}); err != nil {
+		return err
+	}
+	return rethinkdb.CreateAndGrantDBUser(rdb.sess, rdb.dbName, rdb.user, rdb.password)
 }
 
 // CheckHealth verifies that DB exists and is query-able
diff --git a/storage/rethinkdb/bootstrap.go b/storage/rethinkdb/bootstrap.go
index e7cd50f9..16bf4ed5 100644
--- a/storage/rethinkdb/bootstrap.go
+++ b/storage/rethinkdb/bootstrap.go
@@ -146,3 +146,31 @@ func SetupDB(session *gorethink.Session, dbName string, tables []Table) error {
 
 	return nil
 }
+
+// CreateAndGrantDBUser handles creating a rethink user and granting it permissions to the provided db.
+func CreateAndGrantDBUser(session *gorethink.Session, dbName, username, password string) error {
+	var err error
+	logrus.Debugf("creating user %s for db %s", username, dbName)
+	// If the password is empty, pass false to the password parameter
+	if password == "" {
+		err = gorethink.DB("rethinkdb").Table("users").Insert(map[string]interface{}{
+			"id":       username,
+			"password": false,
+		}).Exec(session)
+	} else {
+		err = gorethink.DB("rethinkdb").Table("users").Insert(map[string]string{
+			"id":       username,
+			"password": password,
+		}).Exec(session)
+	}
+
+	if err != nil {
+		return fmt.Errorf("unable to add user %s to rethinkdb users table: %s", username, err)
+	}
+
+	// Grant read and write permission
+	return gorethink.DB(dbName).Grant(username, map[string]bool{
+		"read":  true,
+		"write": true,
+	}).Exec(session)
+}
diff --git a/storage/rethinkdb/rethinkdb.go b/storage/rethinkdb/rethinkdb.go
index 8e7b9420..d7b828af 100644
--- a/storage/rethinkdb/rethinkdb.go
+++ b/storage/rethinkdb/rethinkdb.go
@@ -3,6 +3,7 @@ package rethinkdb
 import (
 	"time"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/docker/go-connections/tlsconfig"
 	"gopkg.in/dancannon/gorethink.v2"
 )
@@ -17,9 +18,10 @@ type Timing struct {
 	DeletedAt time.Time `gorethink:"deleted_at"`
 }
 
-// Connection sets up a RethinkDB connection to the host (`host:port` format)
+// AdminConnection sets up an admin RethinkDB connection to the host (`host:port` format)
 // using the CA .pem file provided at path `caFile`
-func Connection(tlsOpts tlsconfig.Options, host string) (*gorethink.Session, error) {
+func AdminConnection(tlsOpts tlsconfig.Options, host string) (*gorethink.Session, error) {
+	logrus.Debugf("attempting to connect admin to host %s", host)
 	t, err := tlsconfig.Client(tlsOpts)
 	if err != nil {
 		return nil, err
@@ -31,3 +33,21 @@ func Connection(tlsOpts tlsconfig.Options, host string) (*gorethink.Session, err
 		},
 	)
 }
+
+// UserConnection sets up a user RethinkDB connection to the host (`host:port` format)
+// using the CA .pem file provided at path `caFile`, using the provided username.
+func UserConnection(tlsOpts tlsconfig.Options, host, username, password string) (*gorethink.Session, error) {
+	logrus.Debugf("attempting to connect user %s to host %s", username, host)
+	t, err := tlsconfig.Client(tlsOpts)
+	if err != nil {
+		return nil, err
+	}
+	return gorethink.Connect(
+		gorethink.ConnectOpts{
+			Address:   host,
+			TLSConfig: t,
+			Username:  username,
+			Password:  password,
+		},
+	)
+}
diff --git a/utils/configuration.go b/utils/configuration.go
index d0ae8722..9089d198 100644
--- a/utils/configuration.go
+++ b/utils/configuration.go
@@ -26,10 +26,12 @@ type Storage struct {
 // RethinkDBStorage is configuration about a RethinkDB backend service
 type RethinkDBStorage struct {
 	Storage
-	CA     string
-	Cert   string
-	DBName string
-	Key    string
+	CA       string
+	Cert     string
+	DBName   string
+	Key      string
+	Username string
+	Password string
 }
 
 // GetPathRelativeToConfig gets a configuration key which is a path, and if
@@ -118,10 +120,12 @@ func ParseRethinkDBStorage(configuration *viper.Viper) (*RethinkDBStorage, error
 			Backend: configuration.GetString("storage.backend"),
 			Source:  configuration.GetString("storage.db_url"),
 		},
-		CA:     GetPathRelativeToConfig(configuration, "storage.tls_ca_file"),
-		Cert:   GetPathRelativeToConfig(configuration, "storage.client_cert_file"),
-		Key:    GetPathRelativeToConfig(configuration, "storage.client_key_file"),
-		DBName: configuration.GetString("storage.database"),
+		CA:       GetPathRelativeToConfig(configuration, "storage.tls_ca_file"),
+		Cert:     GetPathRelativeToConfig(configuration, "storage.client_cert_file"),
+		Key:      GetPathRelativeToConfig(configuration, "storage.client_key_file"),
+		DBName:   configuration.GetString("storage.database"),
+		Username: configuration.GetString("storage.username"),
+		Password: configuration.GetString("storage.password"),
 	}
 
 	switch {
@@ -150,6 +154,11 @@ func ParseRethinkDBStorage(configuration *viper.Viper) (*RethinkDBStorage, error
 			"%s requires a specific database to connect to",
 			store.Backend,
 		)
+	case store.Username == "":
+		return nil, fmt.Errorf(
+			"%s requires a username to connect to the db",
+			store.Backend,
+		)
 	}
 
 	return &store, nil
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/CHANGELOG.md b/vendor/gopkg.in/dancannon/gorethink.v2/CHANGELOG.md
index 1fc67ed2..be0dd0f9 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/CHANGELOG.md
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/CHANGELOG.md
@@ -2,6 +2,22 @@
 All notable changes to this project will be documented in this file.
 This project adheres to [Semantic Versioning](http://semver.org/).
 
+## v2.0.2 - 2016-04-18
+
+### Fixed
+ - Fixed issue which prevented anonymous `time.Time` values from being encoded when used in a struct.
+ - Fixed panic when attempting to run a query with a nil session
+
+## v2.0.1 - 2016-04-14
+
+### Added
+ - Added `UnionWithOpts` term which allows `Union` to be called with optional arguments (such as `Interleave`)
+ - Added `IncludeOffsets` and `IncludeTypes` optional arguments to `ChangesOpts`
+ - Added `Conflict` optional argument to `InsertOpts`
+
+### Fixed
+ - Fixed error when connecting to database as non-admin user, please note that `DiscoverHosts` will not work with user authentication at this time due to the fact that RethinkDB restricts access to the required system tables.
+
 ## v2.0.0 - 2016-04-13
 
 ### Changed
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/README.md b/vendor/gopkg.in/dancannon/gorethink.v2/README.md
index 7d9bac5f..60ccbf2a 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/README.md
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/README.md
@@ -8,7 +8,7 @@
 
 ![GoRethink Logo](https://raw.github.com/wiki/dancannon/gorethink/gopher-and-thinker-s.png "Golang Gopher and RethinkDB Thinker")
 
-Current version: v2.0.0 (RethinkDB v2.3)
+Current version: v2.0.2 (RethinkDB v2.3)
 
 Please note that this version of the driver only supports versions of RethinkDB using the v0.4 protocol (any versions of the driver older than RethinkDB 2.0 will not work).
 
@@ -16,15 +16,16 @@ If you need any help you can find me on the [RethinkDB slack](http://slack.rethi
 
 ## Installation
 
-```sh
-go get -u github.com/dancannon/gorethink
-```
-
-Or (pinned to the v1.x.x tag)
 ```
 go get gopkg.in/dancannon/gorethink.v2
 ```
 
+(Or v1)
+
+```sh
+go get gopkg.in/dancannon/gorethink.v1
+```
+
 ## Connection
 
 ### Basic Connection
@@ -91,6 +92,35 @@ if err != nil {
 
 When `DiscoverHosts` is true any nodes are added to the cluster after the initial connection then the new node will be added to the pool of available nodes used by GoRethink. Unfortunately the canonical address of each server in the cluster **MUST** be set as otherwise clients will try to connect to the database nodes locally. For more information about how to set a RethinkDB servers canonical address set this page http://www.rethinkdb.com/docs/config-file/.
 
+## User Authentication
+
+To login with a username and password you should first create a user, this can be done by writing to the `users` system table and then grant that user access to any tables or databases they need access to. This queries can also be executed in the RethinkDB admin console.
+
+```go
+err := r.DB("rethinkdb").Table("users").Insert(map[string]string{
+    "id": "john",
+    "password": "p455w0rd",
+}).Exec(session)
+...
+err = r.DB("blog").Table("posts").Grant("john", map[string]bool{
+    "read": true,
+    "write": true,
+}).Exec(session)
+...
+```
+
+Finally the username and password should be passed to `Connect` when creating your session, for example:
+
+```go
+session, err := r.Connect(r.ConnectOpts{
+    Address: "localhost:28015",
+    Database: "blog",
+    Username: "john",
+    Password: "p455w0rd",
+})
+```
+
+Please note that `DiscoverHosts` will not work with user authentication at this time due to the fact that RethinkDB restricts access to the required system tables.
 
 ## Query Functions
 
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/cluster.go b/vendor/gopkg.in/dancannon/gorethink.v2/cluster.go
index f4945bb8..76ae6830 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/cluster.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/cluster.go
@@ -222,28 +222,23 @@ func (c *Cluster) connectNodes(hosts []Host) {
 		}
 		defer conn.Close()
 
-		q, err := newQuery(
-			DB("rethinkdb").Table("server_status"),
-			map[string]interface{}{},
-			c.opts,
-		)
-		if err != nil {
-			Log.Warnf("Error building query: %s", err)
-			continue
-		}
-
-		_, cursor, err := conn.Query(q)
-		if err != nil {
-			Log.Warnf("Error fetching cluster status: %s", err)
-			continue
-		}
-
-		// TODO: connect to seed hosts using `.Server()` to get server ID. Need
-		// some way of making this backwards compatible
+		if c.opts.DiscoverHosts {
+			q, err := newQuery(
+				DB("rethinkdb").Table("server_status"),
+				map[string]interface{}{},
+				c.opts,
+			)
+			if err != nil {
+				Log.Warnf("Error building query: %s", err)
+				continue
+			}
 
-		// TODO: AFTER try to discover hosts
+			_, cursor, err := conn.Query(q)
+			if err != nil {
+				Log.Warnf("Error fetching cluster status: %s", err)
+				continue
+			}
 
-		if c.opts.DiscoverHosts {
 			var results []nodeStatus
 			err = cursor.All(&results)
 			if err != nil {
@@ -265,7 +260,13 @@ func (c *Cluster) connectNodes(hosts []Host) {
 				}
 			}
 		} else {
-			node, err := c.connectNode(host.String(), []Host{host})
+			svrRsp, err := conn.Server()
+			if err != nil {
+				Log.Warnf("Error fetching server ID: %s", err)
+				continue
+			}
+
+			node, err := c.connectNode(svrRsp.ID, []Host{host})
 			if err == nil {
 				if _, ok := nodeSet[node.ID]; !ok {
 					Log.WithFields(logrus.Fields{
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/connection.go b/vendor/gopkg.in/dancannon/gorethink.v2/connection.go
index 93b71d0d..b4e58db0 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/connection.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/connection.go
@@ -110,6 +110,8 @@ func (c *Connection) Query(q Query) (*Response, *Cursor, error) {
 	// Add token if query is a START/NOREPLY_WAIT
 	if q.Type == p.Query_START || q.Type == p.Query_NOREPLY_WAIT || q.Type == p.Query_SERVER_INFO {
 		q.Token = c.nextToken()
+	}
+	if q.Type == p.Query_START || q.Type == p.Query_NOREPLY_WAIT {
 		if c.opts.Database != "" {
 			var err error
 			q.Opts["db"], err = DB(c.opts.Database).build()
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/doc.go b/vendor/gopkg.in/dancannon/gorethink.v2/doc.go
index daa47c46..b6dd8ac5 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/doc.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/doc.go
@@ -1,6 +1,6 @@
 // Package gorethink implements a Go driver for RethinkDB
 //
-// Current version: v2.0.0 (RethinkDB v2.3)
+// Current version: v2.0.2 (RethinkDB v2.3)
 // For more in depth information on how to use RethinkDB check out the API docs
 // at http://rethinkdb.com/api
 package gorethink
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/encoding/cache.go b/vendor/gopkg.in/dancannon/gorethink.v2/encoding/cache.go
index 041497a1..bffb4cdc 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/encoding/cache.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/encoding/cache.go
@@ -6,6 +6,7 @@ import (
 	"reflect"
 	"sort"
 	"sync"
+	"time"
 )
 
 // A field represents a single field found in a struct.
@@ -131,7 +132,7 @@ func typeFields(t reflect.Type) []field {
 				}
 
 				// Record found field and index sequence.
-				if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
+				if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct || isPseudoType(ft) {
 					tagged := name != ""
 					if name == "" {
 						name = sf.Name
@@ -200,6 +201,10 @@ func typeFields(t reflect.Type) []field {
 	return fields
 }
 
+func isPseudoType(t reflect.Type) bool {
+	return t == reflect.TypeOf(time.Time{})
+}
+
 // dominantField looks through the fields, all of which are known to
 // have the same name, to find the single field that dominates the
 // others using Go's embedding rules, modified by the presence of
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/query.go b/vendor/gopkg.in/dancannon/gorethink.v2/query.go
index 1bd88a36..8c04dbce 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/query.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/query.go
@@ -242,6 +242,10 @@ func (t Term) Run(s *Session, optArgs ...RunOpts) (*Cursor, error) {
 		opts = optArgs[0].toMap()
 	}
 
+	if s == nil || !s.IsConnected() {
+		return nil, ErrConnectionClosed
+	}
+
 	q, err := s.newQuery(t, opts)
 	if err != nil {
 		return nil, err
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/query_table.go b/vendor/gopkg.in/dancannon/gorethink.v2/query_table.go
index 2a7f4f38..e9a7df5b 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/query_table.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/query_table.go
@@ -152,6 +152,8 @@ type ChangesOpts struct {
 	Squash              interface{} `gorethink:"squash,omitempty"`
 	IncludeInitial      interface{} `gorethink:"include_initial,omitempty"`
 	IncludeStates       interface{} `gorethink:"include_states,omitempty"`
+	IncludeOffsets      interface{} `gorethink:"include_offsets,omitempty"`
+	IncludeTypes        interface{} `gorethink:"include_types,omitempty"`
 	ChangefeedQueueSize interface{} `gorethink:"changefeed_queue_size,omitempty"`
 }
 
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/query_transformation.go b/vendor/gopkg.in/dancannon/gorethink.v2/query_transformation.go
index 4960f012..56ebe2e7 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/query_transformation.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/query_transformation.go
@@ -151,6 +151,15 @@ func (t Term) IsEmpty(args ...interface{}) Term {
 	return constructMethodTerm(t, "IsEmpty", p.Term_IS_EMPTY, args, map[string]interface{}{})
 }
 
+// UnionOpts contains the optional arguments for the Slice term
+type UnionOpts struct {
+	Interleave interface{} `gorethink:"interleave,omitempty"`
+}
+
+func (o *UnionOpts) toMap() map[string]interface{} {
+	return optArgsToMap(o)
+}
+
 // Union concatenates two sequences.
 func Union(args ...interface{}) Term {
 	return constructRootTerm("Union", p.Term_UNION, args, map[string]interface{}{})
@@ -161,6 +170,18 @@ func (t Term) Union(args ...interface{}) Term {
 	return constructMethodTerm(t, "Union", p.Term_UNION, args, map[string]interface{}{})
 }
 
+// UnionWithOpts like Union concatenates two sequences however allows for optional
+// arguments to be passed.
+func UnionWithOpts(optArgs UnionOpts, args ...interface{}) Term {
+	return constructRootTerm("Union", p.Term_UNION, args, optArgs.toMap())
+}
+
+// UnionWithOpts like Union concatenates two sequences however allows for optional
+// arguments to be passed.
+func (t Term) UnionWithOpts(optArgs UnionOpts, args ...interface{}) Term {
+	return constructMethodTerm(t, "Union", p.Term_UNION, args, optArgs.toMap())
+}
+
 // Sample selects a given number of elements from a sequence with uniform random
 // distribution. Selection is done without replacement.
 func (t Term) Sample(args ...interface{}) Term {
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/query_write.go b/vendor/gopkg.in/dancannon/gorethink.v2/query_write.go
index b8fcd413..1adb7793 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/query_write.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/query_write.go
@@ -30,6 +30,7 @@ type UpdateOpts struct {
 	Durability    interface{} `gorethink:"durability,omitempty"`
 	ReturnChanges interface{} `gorethink:"return_changes,omitempty"`
 	NotAtomic     interface{} `gorethink:"non_atomic,omitempty"`
+	Conflict      interface{} `gorethink:"conflict,omitempty"`
 }
 
 func (o *UpdateOpts) toMap() map[string]interface{} {


Test patch:
diff --git a/cmd/notary-signer/main_test.go b/cmd/notary-signer/main_test.go
index 3b71aede..d1b3a6e6 100644
--- a/cmd/notary-signer/main_test.go
+++ b/cmd/notary-signer/main_test.go
@@ -123,7 +123,9 @@ func TestSetupCryptoServicesRethinkDBStoreNoDefaultAlias(t *testing.T) {
 				"tls_ca_file": "/tls/ca.pem",
 				"client_cert_file": "/tls/cert.pem",
 				"client_key_file": "/tls/key.pem",
-				"database": "rethinkdbtest"
+				"database": "rethinkdbtest",
+				"username": "signer",
+				"password": "password"
 				}
 			}`,
 			notary.RethinkDBBackend)),
@@ -142,7 +144,9 @@ func TestSetupCryptoServicesRethinkDBStoreConnectionFails(t *testing.T) {
 				"tls_ca_file": "../../fixtures/rethinkdb/ca.pem",
 				"client_cert_file": "../../fixtures/rethinkdb/cert.pem",
 				"client_key_file": "../../fixtures/rethinkdb/key.pem",
-				"database": "rethinkdbtest"
+				"database": "rethinkdbtest",
+				"username": "signer",
+				"password": "password"
 				},
 				"default_alias": "timestamp"
 			}`,
diff --git a/utils/configuration_test.go b/utils/configuration_test.go
index ff23707a..9d5a9070 100644
--- a/utils/configuration_test.go
+++ b/utils/configuration_test.go
@@ -217,7 +217,8 @@ func TestParseRethinkStorageDBStoreInvalidBackend(t *testing.T) {
 			"tls_ca_file": "/tls/ca.pem",
 			"client_cert_file": "/tls/cert.pem",
 			"client_key_file": "/tls/key.pem",
-			"database": "rethinkdbtest"
+			"database": "rethinkdbtest",
+			"username": "user"
 		}
 	}`)
 
@@ -234,7 +235,9 @@ func TestParseRethinkStorageDBStoreEmptyDBUrl(t *testing.T) {
 			"tls_ca_file": "/tls/ca.pem",
 			"client_cert_file": "/tls/cert.pem",
 			"client_key_file": "/tls/key.pem",
-			"database": "rethinkdbtest"
+			"database": "rethinkdbtest",
+			"username": "user",
+			"password": "password"
 		}
 	}`)
 
@@ -251,7 +254,8 @@ func TestParseRethinkStorageDBStoreEmptyDBName(t *testing.T) {
 			"db_url": "username:password@tcp(hostname:1234)/dbname",
 			"tls_ca_file": "/tls/ca.pem",
 			"client_cert_file": "/tls/cert.pem",
-			"client_key_file": "/tls/key.pem"
+			"client_key_file": "/tls/key.pem",
+			"username": "user"
 		}
 	}`)
 
@@ -268,7 +272,8 @@ func TestParseRethinkStorageDBStoreEmptyCA(t *testing.T) {
 			"db_url": "username:password@tcp(hostname:1234)/dbname",
 			"database": "rethinkdbtest",
 			"client_cert_file": "/tls/cert.pem",
-			"client_key_file": "/tls/key.pem"
+			"client_key_file": "/tls/key.pem",
+			"username": "user"
 		}
 	}`)
 
@@ -284,7 +289,8 @@ func TestParseRethinkStorageDBStoreEmptyCertAndKey(t *testing.T) {
 			"backend": "rethinkdb",
 			"db_url": "username:password@tcp(hostname:1234)/dbname",
 			"database": "rethinkdbtest",
-			"tls_ca_file": "/tls/ca.pem"
+			"tls_ca_file": "/tls/ca.pem",
+			"username": "user"
 		}
 	}`)
 
@@ -293,6 +299,24 @@ func TestParseRethinkStorageDBStoreEmptyCertAndKey(t *testing.T) {
 	require.Contains(t, err.Error(), "cowardly refusal to connect to rethinkdb without a client cert")
 }
 
+// ParseRethinkDBStorage will require a username to connect to the database after bootstrapping
+func TestParseRethinkStorageDBStoreEmptyUsername(t *testing.T) {
+	config := configure(`{
+		"storage": {
+			"backend": "rethinkdb",
+			"db_url": "username:password@tcp(hostname:1234)/dbname",
+			"database": "rethinkdbtest",
+			"client_cert_file": "/tls/cert.pem",
+			"client_key_file": "/tls/key.pem",
+			"tls_ca_file": "/tls/ca.pem"
+		}
+	}`)
+
+	_, err := ParseRethinkDBStorage(config)
+	require.Error(t, err)
+	require.Contains(t, err.Error(), "requires a username to connect to the db")
+}
+
 func TestParseSQLStorageWithEnvironmentVariables(t *testing.T) {
 	config := configure(`{
 		"storage": {
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/query_test.go b/vendor/gopkg.in/dancannon/gorethink.v2/query_test.go
index 486be131..93372505 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/query_test.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/query_test.go
@@ -65,3 +65,17 @@ func (s *RethinkSuite) TestQueryRunRawTime(c *test.C) {
 	c.Assert(response["$reql_type$"], test.NotNil)
 	c.Assert(response["$reql_type$"], test.Equals, "TIME")
 }
+
+func (s *RethinkSuite) TestQueryRunNil(c *test.C) {
+	res, err := Expr("Test").Run(nil)
+	c.Assert(res, test.IsNil)
+	c.Assert(err, test.NotNil)
+	c.Assert(err, test.Equals, ErrConnectionClosed)
+}
+
+func (s *RethinkSuite) TestQueryRunNotConnected(c *test.C) {
+	res, err := Expr("Test").Run(&Session{})
+	c.Assert(res, test.IsNil)
+	c.Assert(err, test.NotNil)
+	c.Assert(err, test.Equals, ErrConnectionClosed)
+}
\ No newline at end of file
diff --git a/vendor/gopkg.in/dancannon/gorethink.v2/session_test.go b/vendor/gopkg.in/dancannon/gorethink.v2/session_test.go
index 59503823..53b27de1 100644
--- a/vendor/gopkg.in/dancannon/gorethink.v2/session_test.go
+++ b/vendor/gopkg.in/dancannon/gorethink.v2/session_test.go
@@ -129,3 +129,25 @@ func (s *RethinkSuite) TestSessionConnectDatabase(c *test.C) {
 	c.Assert(err, test.NotNil)
 	c.Assert(err.Error(), test.Equals, "gorethink: Database `test2` does not exist. in: \nr.Table(\"test2\")")
 }
+
+func (s *RethinkSuite) TestSessionConnectUsername(c *test.C) {
+	session, err := Connect(ConnectOpts{
+		Address: url,
+	})
+	c.Assert(err, test.IsNil)
+
+	DB("rethinkdb").Table("users").Insert(map[string]string{
+		"id":       "gorethink_test",
+		"password": "password",
+	}).Exec(session)
+
+	session, err = Connect(ConnectOpts{
+		Address:  url,
+		Username: "gorethink_test",
+		Password: "password",
+	})
+	c.Assert(err, test.IsNil)
+
+	_, err = Expr("Hello World").Run(session)
+	c.Assert(err, test.IsNil)
+}