mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	 35667f93a7
			
		
	
	35667f93a7
	
	
	
		
			
			* Add priority queue to sdk * fix issue of storing pointers and now copy * update to use copy structure * Remove file, put Item struct def. into other file * add link * clean up docs * refactor internal data structure to hide heap method implementations. Other cleanup after feedback * rename PushItem and PopItem to just Push/Pop, after encapsulating the heap methods * updates after feedback * refactoring/renaming * guard against pushing a nil item * minor updates after feedback * Add SetCredentials, GenerateCredentials gRPC methods to combined database backend gPRC * Initial Combined database backend implementation of static accounts and automatic rotation * vendor updates * initial implementation of static accounts with Combined database backend, starting with PostgreSQL implementation * add lock and setup of rotation queue * vendor the queue * rebase on new method signature of queue * remove mongo tests for now * update default role sql * gofmt after rebase * cleanup after rebasing to remove checks for ErrNotFound error * rebase cdcr-priority-queue * vendor dependencies with 'go mod vendor' * website database docs for Static Role support * document the rotate-role API endpoint * postgres specific static role docs * use constants for paths * updates from review * remove dead code * combine and clarify error message for older plugins * Update builtin/logical/database/backend.go Co-Authored-By: Jim Kalafut <jim@kalafut.net> * cleanups from feedback * code and comment cleanups * move db.RLock higher to protect db.GenerateCredentials call * Return output with WALID if we failed to delete the WAL * Update builtin/logical/database/path_creds_create.go Co-Authored-By: Jim Kalafut <jim@kalafut.net> * updates after running 'make fmt' * update after running 'make proto' * Update builtin/logical/database/path_roles.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * Update builtin/logical/database/path_roles.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * update comment and remove and rearrange some dead code * Update website/source/api/secret/databases/index.html.md Co-Authored-By: Jim Kalafut <jim@kalafut.net> * cleanups after review * Update sdk/database/dbplugin/grpc_transport.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * code cleanup after feedback * remove PasswordLastSet; it's not used * document GenerateCredentials and SetCredentials * Update builtin/logical/database/path_rotate_credentials.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * wrap pop and popbykey in backend methods to protect against nil cred rotation queue * use strings.HasPrefix instead of direct equality check for path * Forgot to commit this * updates after feedback * re-purpose an outdated test to now check that static and dynamic roles cannot share a name * check for unique name across dynamic and static roles * refactor loadStaticWALs to return a map of name/setCredentialsWAL struct to consolidate where we're calling set credentials * remove commented out code * refactor to have loadstaticwals filter out wals for roles that no longer exist * return error if nil input given * add nil check for input into setStaticAccount * Update builtin/logical/database/path_roles.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * add constant for queue tick time in seconds, used for comparrison in updates * Update builtin/logical/database/path_roles.go Co-Authored-By: Jim Kalafut <jim@kalafut.net> * code cleanup after review * remove misplaced code comment * remove commented out code * create a queue in the Factory method, even if it's never used * update path_roles to use a common set of fields, with specific overrides for dynamic/static roles by type * document new method * move rotation things into a specific file * rename test file and consolidate some static account tests * Update builtin/logical/database/path_roles.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * Update builtin/logical/database/rotation.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * Update builtin/logical/database/rotation.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * Update builtin/logical/database/rotation.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * Update builtin/logical/database/rotation.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * Update builtin/logical/database/rotation.go Co-Authored-By: Brian Kassouf <briankassouf@users.noreply.github.com> * update code comments, method names, and move more methods into rotation.go * update comments to be capitalized * remove the item from the queue before we try to destroy it * findStaticWAL returns an error * use lowercase keys when encoding WAL entries * small cleanups * remove vestigial static account check * remove redundant DeleteWAL call in populate queue * if we error on loading role, push back to queue with 10 second backoff * poll in initqueue to make sure the backend is setup and can write/delete data * add revoke_user_on_delete flag to allow users to opt-in to revoking the static database user on delete of the Vault role. Default false * add code comments on read-only loop * code comment updates * re-push if error returned from find static wal * add locksutil and acquire locks when pop'ing from the queue * grab exclusive locks for updating static roles * Add SetCredentials and GenerateCredentials stubs to mockPlugin * add a switch in initQueue to listen for cancelation * remove guard on zero time, it should have no affect * create a new context in Factory to pass on and use for closing the backend queue * restore master copy of vendor dir
		
			
				
	
	
		
			240 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			240 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package mongodb
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/tls"
 | |
| 	"encoding/base64"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"net/url"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/hashicorp/errwrap"
 | |
| 	"github.com/hashicorp/vault/sdk/database/dbplugin"
 | |
| 	"github.com/hashicorp/vault/sdk/database/helper/connutil"
 | |
| 	"github.com/hashicorp/vault/sdk/database/helper/dbutil"
 | |
| 	"github.com/mitchellh/mapstructure"
 | |
| 
 | |
| 	mgo "gopkg.in/mgo.v2"
 | |
| )
 | |
| 
 | |
| // mongoDBConnectionProducer implements ConnectionProducer and provides an
 | |
| // interface for databases to make connections.
 | |
| type mongoDBConnectionProducer struct {
 | |
| 	ConnectionURL string `json:"connection_url" structs:"connection_url" mapstructure:"connection_url"`
 | |
| 	WriteConcern  string `json:"write_concern" structs:"write_concern" mapstructure:"write_concern"`
 | |
| 	Username      string `json:"username" structs:"username" mapstructure:"username"`
 | |
| 	Password      string `json:"password" structs:"password" mapstructure:"password"`
 | |
| 
 | |
| 	Initialized bool
 | |
| 	RawConfig   map[string]interface{}
 | |
| 	Type        string
 | |
| 	session     *mgo.Session
 | |
| 	safe        *mgo.Safe
 | |
| 	sync.Mutex
 | |
| }
 | |
| 
 | |
| func (c *mongoDBConnectionProducer) Initialize(ctx context.Context, conf map[string]interface{}, verifyConnection bool) error {
 | |
| 	_, err := c.Init(ctx, conf, verifyConnection)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // Initialize parses connection configuration.
 | |
| func (c *mongoDBConnectionProducer) Init(ctx context.Context, conf map[string]interface{}, verifyConnection bool) (map[string]interface{}, error) {
 | |
| 	c.Lock()
 | |
| 	defer c.Unlock()
 | |
| 
 | |
| 	c.RawConfig = conf
 | |
| 
 | |
| 	err := mapstructure.WeakDecode(conf, c)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if len(c.ConnectionURL) == 0 {
 | |
| 		return nil, fmt.Errorf("connection_url cannot be empty")
 | |
| 	}
 | |
| 
 | |
| 	c.ConnectionURL = dbutil.QueryHelper(c.ConnectionURL, map[string]string{
 | |
| 		"username": c.Username,
 | |
| 		"password": c.Password,
 | |
| 	})
 | |
| 
 | |
| 	if c.WriteConcern != "" {
 | |
| 		input := c.WriteConcern
 | |
| 
 | |
| 		// Try to base64 decode the input. If successful, consider the decoded
 | |
| 		// value as input.
 | |
| 		inputBytes, err := base64.StdEncoding.DecodeString(input)
 | |
| 		if err == nil {
 | |
| 			input = string(inputBytes)
 | |
| 		}
 | |
| 
 | |
| 		concern := &mgo.Safe{}
 | |
| 		err = json.Unmarshal([]byte(input), concern)
 | |
| 		if err != nil {
 | |
| 			return nil, errwrap.Wrapf("error mashalling write_concern: {{err}}", err)
 | |
| 		}
 | |
| 
 | |
| 		// Guard against empty, non-nil mgo.Safe object; we don't want to pass that
 | |
| 		// into mgo.SetSafe in Connection().
 | |
| 		if (mgo.Safe{} == *concern) {
 | |
| 			return nil, fmt.Errorf("provided write_concern values did not map to any mgo.Safe fields")
 | |
| 		}
 | |
| 		c.safe = concern
 | |
| 	}
 | |
| 
 | |
| 	// Set initialized to true at this point since all fields are set,
 | |
| 	// and the connection can be established at a later time.
 | |
| 	c.Initialized = true
 | |
| 
 | |
| 	if verifyConnection {
 | |
| 		if _, err := c.Connection(ctx); err != nil {
 | |
| 			return nil, errwrap.Wrapf("error verifying connection: {{err}}", err)
 | |
| 		}
 | |
| 
 | |
| 		if err := c.session.Ping(); err != nil {
 | |
| 			return nil, errwrap.Wrapf("error verifying connection: {{err}}", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return conf, nil
 | |
| }
 | |
| 
 | |
| // Connection creates or returns an existing a database connection. If the session fails
 | |
| // on a ping check, the session will be closed and then re-created.
 | |
| func (c *mongoDBConnectionProducer) Connection(_ context.Context) (interface{}, error) {
 | |
| 	if !c.Initialized {
 | |
| 		return nil, connutil.ErrNotInitialized
 | |
| 	}
 | |
| 
 | |
| 	if c.session != nil {
 | |
| 		if err := c.session.Ping(); err == nil {
 | |
| 			return c.session, nil
 | |
| 		}
 | |
| 		c.session.Close()
 | |
| 	}
 | |
| 
 | |
| 	dialInfo, err := parseMongoURL(c.ConnectionURL)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	c.session, err = mgo.DialWithInfo(dialInfo)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if c.safe != nil {
 | |
| 		c.session.SetSafe(c.safe)
 | |
| 	}
 | |
| 
 | |
| 	c.session.SetSyncTimeout(1 * time.Minute)
 | |
| 	c.session.SetSocketTimeout(1 * time.Minute)
 | |
| 
 | |
| 	return c.session, nil
 | |
| }
 | |
| 
 | |
| // Close terminates the database connection.
 | |
| func (c *mongoDBConnectionProducer) Close() error {
 | |
| 	c.Lock()
 | |
| 	defer c.Unlock()
 | |
| 
 | |
| 	if c.session != nil {
 | |
| 		c.session.Close()
 | |
| 	}
 | |
| 
 | |
| 	c.session = nil
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SetCredentials uses provided information to set/create a user in the
 | |
| // database. Unlike CreateUser, this method requires a username be provided and
 | |
| // uses the name given, instead of generating a name. This is used for creating
 | |
| // and setting the password of static accounts, as well as rolling back
 | |
| // passwords in the database in the event an updated database fails to save in
 | |
| // Vault's storage.
 | |
| func (c *mongoDBConnectionProducer) SetCredentials(ctx context.Context, statements dbplugin.Statements, staticUser dbplugin.StaticUserConfig) (username, password string, err error) {
 | |
| 	return "", "", dbutil.Unimplemented()
 | |
| }
 | |
| 
 | |
| func parseMongoURL(rawURL string) (*mgo.DialInfo, error) {
 | |
| 	url, err := url.Parse(rawURL)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	info := mgo.DialInfo{
 | |
| 		Addrs:    strings.Split(url.Host, ","),
 | |
| 		Database: strings.TrimPrefix(url.Path, "/"),
 | |
| 		Timeout:  10 * time.Second,
 | |
| 	}
 | |
| 
 | |
| 	if url.User != nil {
 | |
| 		info.Username = url.User.Username()
 | |
| 		info.Password, _ = url.User.Password()
 | |
| 	}
 | |
| 
 | |
| 	query := url.Query()
 | |
| 	for key, values := range query {
 | |
| 		var value string
 | |
| 		if len(values) > 0 {
 | |
| 			value = values[0]
 | |
| 		}
 | |
| 
 | |
| 		switch key {
 | |
| 		case "authSource":
 | |
| 			info.Source = value
 | |
| 		case "authMechanism":
 | |
| 			info.Mechanism = value
 | |
| 		case "gssapiServiceName":
 | |
| 			info.Service = value
 | |
| 		case "replicaSet":
 | |
| 			info.ReplicaSetName = value
 | |
| 		case "maxPoolSize":
 | |
| 			poolLimit, err := strconv.Atoi(value)
 | |
| 			if err != nil {
 | |
| 				return nil, errors.New("bad value for maxPoolSize: " + value)
 | |
| 			}
 | |
| 			info.PoolLimit = poolLimit
 | |
| 		case "ssl":
 | |
| 			// Unfortunately, mgo doesn't support the ssl parameter in its MongoDB URI parsing logic, so we have to handle that
 | |
| 			// ourselves. See https://github.com/go-mgo/mgo/issues/84
 | |
| 			ssl, err := strconv.ParseBool(value)
 | |
| 			if err != nil {
 | |
| 				return nil, errors.New("bad value for ssl: " + value)
 | |
| 			}
 | |
| 			if ssl {
 | |
| 				info.DialServer = func(addr *mgo.ServerAddr) (net.Conn, error) {
 | |
| 					return tls.Dial("tcp", addr.String(), &tls.Config{})
 | |
| 				}
 | |
| 			}
 | |
| 		case "connect":
 | |
| 			if value == "direct" {
 | |
| 				info.Direct = true
 | |
| 				break
 | |
| 			}
 | |
| 			if value == "replicaSet" {
 | |
| 				break
 | |
| 			}
 | |
| 			fallthrough
 | |
| 		default:
 | |
| 			return nil, errors.New("unsupported connection URL option: " + key + "=" + value)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return &info, nil
 | |
| }
 | |
| 
 | |
| func (c *mongoDBConnectionProducer) secretValues() map[string]interface{} {
 | |
| 	return map[string]interface{}{
 | |
| 		c.Password: "[password]",
 | |
| 	}
 | |
| }
 |