diff --git a/command/commands.go b/command/commands.go index 6ea4511ad..8cf9dac0c 100644 --- a/command/commands.go +++ b/command/commands.go @@ -41,7 +41,6 @@ import ( logicalKv "github.com/hashicorp/vault-plugin-secrets-kv" logicalDb "github.com/hashicorp/vault/builtin/logical/database" - physCockroachDB "github.com/hashicorp/vault/physical/cockroachdb" physConsul "github.com/hashicorp/vault/physical/consul" physFoundationDB "github.com/hashicorp/vault/physical/foundationdb" physMySQL "github.com/hashicorp/vault/physical/mysql" @@ -167,7 +166,6 @@ var ( } physicalBackends = map[string]physical.Factory{ - "cockroachdb": physCockroachDB.NewCockroachDBBackend, "consul": physConsul.NewConsulBackend, "file_transactional": physFile.NewTransactionalFileBackend, "file": physFile.NewFileBackend, diff --git a/go.mod b/go.mod index e51c66fb7..b4b5c2b92 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/axiomhq/hyperloglog v0.0.0-20220105174342-98591331716a github.com/cenkalti/backoff/v3 v3.2.2 github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0 - github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 github.com/dustin/go-humanize v1.0.1 @@ -295,7 +294,6 @@ require ( github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect - github.com/jackc/pgx v3.3.0+incompatible // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect diff --git a/go.sum b/go.sum index 0869bef9d..999497474 100644 --- a/go.sum +++ b/go.sum @@ -1095,8 +1095,6 @@ github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= -github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= @@ -2092,7 +2090,6 @@ github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9 github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= @@ -2128,7 +2125,6 @@ github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrU github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx v3.3.0+incompatible h1:Wa90/+qsITBAPkAZjiByeIGHFcj3Ztu+VzrrIpHjL90= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= @@ -2683,7 +2679,6 @@ github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiB github.com/safchain/ethtool v0.2.0/go.mod h1:WkKB1DnNtvsMlDmQ50sgwowDJV/hGbJSOvJoEXs1AJQ= github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y= github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= diff --git a/physical/cockroachdb/cockroachdb.go b/physical/cockroachdb/cockroachdb.go deleted file mode 100644 index 3258ecb94..000000000 --- a/physical/cockroachdb/cockroachdb.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package cockroachdb - -import ( - "context" - "database/sql" - "fmt" - "sort" - "strconv" - "strings" - "time" - "unicode" - - metrics "github.com/armon/go-metrics" - "github.com/cockroachdb/cockroach-go/crdb" - log "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/go-secure-stdlib/strutil" - "github.com/hashicorp/vault/sdk/physical" - - // CockroachDB uses the Postgres SQL driver - _ "github.com/jackc/pgx/v4/stdlib" -) - -// Verify CockroachDBBackend satisfies the correct interfaces -var ( - _ physical.Backend = (*CockroachDBBackend)(nil) - _ physical.Transactional = (*CockroachDBBackend)(nil) -) - -const ( - defaultTableName = "vault_kv_store" - defaultHATableName = "vault_ha_locks" -) - -// CockroachDBBackend Backend is a physical backend that stores data -// within a CockroachDB database. -type CockroachDBBackend struct { - table string - haTable string - client *sql.DB - rawStatements map[string]string - statements map[string]*sql.Stmt - rawHAStatements map[string]string - haStatements map[string]*sql.Stmt - logger log.Logger - permitPool *physical.PermitPool - haEnabled bool -} - -// NewCockroachDBBackend constructs a CockroachDB backend using the given -// API client, server address, credentials, and database. -func NewCockroachDBBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) { - // Get the CockroachDB credentials to perform read/write operations. - connURL, ok := conf["connection_url"] - if !ok || connURL == "" { - return nil, fmt.Errorf("missing connection_url") - } - - haEnabled := conf["ha_enabled"] == "true" - - dbTable := conf["table"] - if dbTable == "" { - dbTable = defaultTableName - } - - err := validateDBTable(dbTable) - if err != nil { - return nil, fmt.Errorf("invalid table: %w", err) - } - - dbHATable, ok := conf["ha_table"] - if !ok { - dbHATable = defaultHATableName - } - - err = validateDBTable(dbHATable) - if err != nil { - return nil, fmt.Errorf("invalid HA table: %w", err) - } - - maxParStr, ok := conf["max_parallel"] - var maxParInt int - if ok { - maxParInt, err = strconv.Atoi(maxParStr) - if err != nil { - return nil, fmt.Errorf("failed parsing max_parallel parameter: %w", err) - } - if logger.IsDebug() { - logger.Debug("max_parallel set", "max_parallel", maxParInt) - } - } - - // Create CockroachDB handle for the database. - db, err := sql.Open("pgx", connURL) - if err != nil { - return nil, fmt.Errorf("failed to connect to cockroachdb: %w", err) - } - - // Create the required tables if they don't exist. - createQuery := "CREATE TABLE IF NOT EXISTS " + dbTable + - " (path STRING, value BYTES, PRIMARY KEY (path))" - if _, err := db.Exec(createQuery); err != nil { - return nil, fmt.Errorf("failed to create CockroachDB table: %w", err) - } - if haEnabled { - createHATableQuery := "CREATE TABLE IF NOT EXISTS " + dbHATable + - "(ha_key TEXT NOT NULL, " + - " ha_identity TEXT NOT NULL, " + - " ha_value TEXT, " + - " valid_until TIMESTAMP WITH TIME ZONE NOT NULL, " + - " CONSTRAINT ha_key PRIMARY KEY (ha_key) " + - ");" - if _, err := db.Exec(createHATableQuery); err != nil { - return nil, fmt.Errorf("failed to create CockroachDB HA table: %w", err) - } - } - - // Setup the backend - c := &CockroachDBBackend{ - table: dbTable, - haTable: dbHATable, - client: db, - rawStatements: map[string]string{ - "put": "INSERT INTO " + dbTable + " VALUES($1, $2)" + - " ON CONFLICT (path) DO " + - " UPDATE SET (path, value) = ($1, $2)", - "get": "SELECT value FROM " + dbTable + " WHERE path = $1", - "delete": "DELETE FROM " + dbTable + " WHERE path = $1", - "list": "SELECT path FROM " + dbTable + " WHERE path LIKE $1", - }, - statements: make(map[string]*sql.Stmt), - rawHAStatements: map[string]string{ - "get": "SELECT ha_value FROM " + dbHATable + " WHERE NOW() <= valid_until AND ha_key = $1", - "upsert": "INSERT INTO " + dbHATable + " as t (ha_identity, ha_key, ha_value, valid_until)" + - " VALUES ($1, $2, $3, NOW() + $4) " + - " ON CONFLICT (ha_key) DO " + - " UPDATE SET (ha_identity, ha_key, ha_value, valid_until) = ($1, $2, $3, NOW() + $4) " + - " WHERE (t.valid_until < NOW() AND t.ha_key = $2) OR " + - " (t.ha_identity = $1 AND t.ha_key = $2) ", - "delete": "DELETE FROM " + dbHATable + " WHERE ha_key = $1", - }, - haStatements: make(map[string]*sql.Stmt), - logger: logger, - permitPool: physical.NewPermitPool(maxParInt), - haEnabled: haEnabled, - } - - // Prepare all the statements required - for name, query := range c.rawStatements { - if err := c.prepare(c.statements, name, query); err != nil { - return nil, err - } - } - if haEnabled { - for name, query := range c.rawHAStatements { - if err := c.prepare(c.haStatements, name, query); err != nil { - return nil, err - } - } - } - return c, nil -} - -// prepare is a helper to prepare a query for future execution. -func (c *CockroachDBBackend) prepare(statementMap map[string]*sql.Stmt, name, query string) error { - stmt, err := c.client.Prepare(query) - if err != nil { - return fmt.Errorf("failed to prepare %q: %w", name, err) - } - statementMap[name] = stmt - return nil -} - -// Put is used to insert or update an entry. -func (c *CockroachDBBackend) Put(ctx context.Context, entry *physical.Entry) error { - defer metrics.MeasureSince([]string{"cockroachdb", "put"}, time.Now()) - - c.permitPool.Acquire() - defer c.permitPool.Release() - - _, err := c.statements["put"].Exec(entry.Key, entry.Value) - if err != nil { - return err - } - return nil -} - -// Get is used to fetch and entry. -func (c *CockroachDBBackend) Get(ctx context.Context, key string) (*physical.Entry, error) { - defer metrics.MeasureSince([]string{"cockroachdb", "get"}, time.Now()) - - c.permitPool.Acquire() - defer c.permitPool.Release() - - var result []byte - err := c.statements["get"].QueryRow(key).Scan(&result) - if err == sql.ErrNoRows { - return nil, nil - } - if err != nil { - return nil, err - } - - ent := &physical.Entry{ - Key: key, - Value: result, - } - return ent, nil -} - -// Delete is used to permanently delete an entry -func (c *CockroachDBBackend) Delete(ctx context.Context, key string) error { - defer metrics.MeasureSince([]string{"cockroachdb", "delete"}, time.Now()) - - c.permitPool.Acquire() - defer c.permitPool.Release() - - _, err := c.statements["delete"].Exec(key) - if err != nil { - return err - } - return nil -} - -// List is used to list all the keys under a given -// prefix, up to the next prefix. -func (c *CockroachDBBackend) List(ctx context.Context, prefix string) ([]string, error) { - defer metrics.MeasureSince([]string{"cockroachdb", "list"}, time.Now()) - - c.permitPool.Acquire() - defer c.permitPool.Release() - - likePrefix := prefix + "%" - rows, err := c.statements["list"].Query(likePrefix) - if err != nil { - return nil, err - } - defer rows.Close() - - var keys []string - for rows.Next() { - var key string - err = rows.Scan(&key) - if err != nil { - return nil, fmt.Errorf("failed to scan rows: %w", err) - } - - key = strings.TrimPrefix(key, prefix) - if i := strings.Index(key, "/"); i == -1 { - // Add objects only from the current 'folder' - keys = append(keys, key) - } else if i != -1 { - // Add truncated 'folder' paths - keys = strutil.AppendIfMissing(keys, string(key[:i+1])) - } - } - - sort.Strings(keys) - return keys, nil -} - -// Transaction is used to run multiple entries via a transaction -func (c *CockroachDBBackend) Transaction(ctx context.Context, txns []*physical.TxnEntry) error { - defer metrics.MeasureSince([]string{"cockroachdb", "transaction"}, time.Now()) - if len(txns) == 0 { - return nil - } - - c.permitPool.Acquire() - defer c.permitPool.Release() - - return crdb.ExecuteTx(context.Background(), c.client, nil, func(tx *sql.Tx) error { - return c.transaction(tx, txns) - }) -} - -func (c *CockroachDBBackend) transaction(tx *sql.Tx, txns []*physical.TxnEntry) error { - deleteStmt, err := tx.Prepare(c.rawStatements["delete"]) - if err != nil { - return err - } - putStmt, err := tx.Prepare(c.rawStatements["put"]) - if err != nil { - return err - } - - for _, op := range txns { - switch op.Operation { - case physical.DeleteOperation: - _, err = deleteStmt.Exec(op.Entry.Key) - case physical.PutOperation: - _, err = putStmt.Exec(op.Entry.Key, op.Entry.Value) - default: - return fmt.Errorf("%q is not a supported transaction operation", op.Operation) - } - if err != nil { - return err - } - } - return nil -} - -// validateDBTable against the CockroachDB rules for table names: -// https://www.cockroachlabs.com/docs/stable/keywords-and-identifiers.html#identifiers -// -// - All values that accept an identifier must: -// - Begin with a Unicode letter or an underscore (_). Subsequent characters can be letters, -// - underscores, digits (0-9), or dollar signs ($). -// - Not equal any SQL keyword unless the keyword is accepted by the element's syntax. For example, -// name accepts Unreserved or Column Name keywords. -// -// The docs do state that we can bypass these rules with double quotes, however I think it -// is safer to just require these rules across the board. -func validateDBTable(dbTable string) (err error) { - // Check if this is 'database.table' formatted. If so, split them apart and check the two - // parts from each other - split := strings.SplitN(dbTable, ".", 2) - if len(split) == 2 { - merr := &multierror.Error{} - merr = multierror.Append(merr, wrapErr("invalid database: %w", validateDBTable(split[0]))) - merr = multierror.Append(merr, wrapErr("invalid table name: %w", validateDBTable(split[1]))) - return merr.ErrorOrNil() - } - - // Disallow SQL keywords as the table name - if sqlKeywords[strings.ToUpper(dbTable)] { - return fmt.Errorf("name must not be a SQL keyword") - } - - runes := []rune(dbTable) - for i, r := range runes { - if i == 0 && !unicode.IsLetter(r) && r != '_' { - return fmt.Errorf("must use a letter or an underscore as the first character") - } - - if !unicode.IsLetter(r) && r != '_' && !unicode.IsDigit(r) && r != '$' { - return fmt.Errorf("must only contain letters, underscores, digits, and dollar signs") - } - - if r == '`' || r == '\'' || r == '"' { - return fmt.Errorf("cannot contain backticks, single quotes, or double quotes") - } - } - - return nil -} - -func wrapErr(message string, err error) error { - if err == nil { - return nil - } - return fmt.Errorf(message, err) -} diff --git a/physical/cockroachdb/cockroachdb_ha.go b/physical/cockroachdb/cockroachdb_ha.go deleted file mode 100644 index 03728d63c..000000000 --- a/physical/cockroachdb/cockroachdb_ha.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package cockroachdb - -import ( - "database/sql" - "fmt" - "sync" - "time" - - "github.com/hashicorp/go-uuid" - "github.com/hashicorp/vault/sdk/physical" -) - -const ( - // The lock TTL matches the default that Consul API uses, 15 seconds. - // Used as part of SQL commands to set/extend lock expiry time relative to - // database clock. - CockroachDBLockTTLSeconds = 15 - - // The amount of time to wait between the lock renewals - CockroachDBLockRenewInterval = 5 * time.Second - - // CockroachDBLockRetryInterval is the amount of time to wait - // if a lock fails before trying again. - CockroachDBLockRetryInterval = time.Second -) - -// Verify backend satisfies the correct interfaces. -var ( - _ physical.HABackend = (*CockroachDBBackend)(nil) - _ physical.Lock = (*CockroachDBLock)(nil) -) - -type CockroachDBLock struct { - backend *CockroachDBBackend - key string - value string - identity string - lock sync.Mutex - - renewTicker *time.Ticker - - // ttlSeconds is how long a lock is valid for. - ttlSeconds int - - // renewInterval is how much time to wait between lock renewals. must be << ttl. - renewInterval time.Duration - - // retryInterval is how much time to wait between attempts to grab the lock. - retryInterval time.Duration -} - -func (c *CockroachDBBackend) HAEnabled() bool { - return c.haEnabled -} - -func (c *CockroachDBBackend) LockWith(key, value string) (physical.Lock, error) { - identity, err := uuid.GenerateUUID() - if err != nil { - return nil, err - } - return &CockroachDBLock{ - backend: c, - key: key, - value: value, - identity: identity, - ttlSeconds: CockroachDBLockTTLSeconds, - renewInterval: CockroachDBLockRenewInterval, - retryInterval: CockroachDBLockRetryInterval, - }, nil -} - -// Lock tries to acquire the lock by repeatedly trying to create a record in the -// CockroachDB table. It will block until either the stop channel is closed or -// the lock could be acquired successfully. The returned channel will be closed -// once the lock in the CockroachDB table cannot be renewed, either due to an -// error speaking to CockroachDB or because someone else has taken it. -func (l *CockroachDBLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) { - l.lock.Lock() - defer l.lock.Unlock() - - var ( - success = make(chan struct{}) - errors = make(chan error, 1) - leader = make(chan struct{}) - ) - go l.tryToLock(stopCh, success, errors) - - select { - case <-success: - // After acquiring it successfully, we must renew the lock periodically. - l.renewTicker = time.NewTicker(l.renewInterval) - go l.periodicallyRenewLock(leader) - case err := <-errors: - return nil, err - case <-stopCh: - return nil, nil - } - - return leader, nil -} - -// Unlock releases the lock by deleting the lock record from the -// CockroachDB table. -func (l *CockroachDBLock) Unlock() error { - c := l.backend - c.permitPool.Acquire() - defer c.permitPool.Release() - - if l.renewTicker != nil { - l.renewTicker.Stop() - } - - _, err := c.haStatements["delete"].Exec(l.key) - return err -} - -// Value checks whether or not the lock is held by any instance of CockroachDBLock, -// including this one, and returns the current value. -func (l *CockroachDBLock) Value() (bool, string, error) { - c := l.backend - c.permitPool.Acquire() - defer c.permitPool.Release() - var result string - err := c.haStatements["get"].QueryRow(l.key).Scan(&result) - - switch err { - case nil: - return true, result, nil - case sql.ErrNoRows: - return false, "", nil - default: - return false, "", err - - } -} - -// tryToLock tries to create a new item in CockroachDB every `retryInterval`. -// As long as the item cannot be created (because it already exists), it will -// be retried. If the operation fails due to an error, it is sent to the errors -// channel. When the lock could be acquired successfully, the success channel -// is closed. -func (l *CockroachDBLock) tryToLock(stop <-chan struct{}, success chan struct{}, errors chan error) { - ticker := time.NewTicker(l.retryInterval) - defer ticker.Stop() - - for { - select { - case <-stop: - return - case <-ticker.C: - gotlock, err := l.writeItem() - switch { - case err != nil: - // Send to the error channel and don't block if full. - select { - case errors <- err: - default: - } - return - case gotlock: - close(success) - return - } - } - } -} - -func (l *CockroachDBLock) periodicallyRenewLock(done chan struct{}) { - for range l.renewTicker.C { - gotlock, err := l.writeItem() - if err != nil || !gotlock { - close(done) - l.renewTicker.Stop() - return - } - } -} - -// Attempts to put/update the CockroachDB item using condition expressions to -// evaluate the TTL. Returns true if the lock was obtained, false if not. -// If false error may be nil or non-nil: nil indicates simply that someone -// else has the lock, whereas non-nil means that something unexpected happened. -func (l *CockroachDBLock) writeItem() (bool, error) { - c := l.backend - c.permitPool.Acquire() - defer c.permitPool.Release() - - sqlResult, err := c.haStatements["upsert"].Exec(l.identity, l.key, l.value, fmt.Sprintf("%d seconds", l.ttlSeconds)) - if err != nil { - return false, err - } - if sqlResult == nil { - return false, fmt.Errorf("empty SQL response received") - } - - ar, err := sqlResult.RowsAffected() - if err != nil { - return false, err - } - return ar == 1, nil -} diff --git a/physical/cockroachdb/cockroachdb_test.go b/physical/cockroachdb/cockroachdb_test.go deleted file mode 100644 index f7ba9e0f2..000000000 --- a/physical/cockroachdb/cockroachdb_test.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package cockroachdb - -import ( - "context" - "database/sql" - "fmt" - "net/url" - "os" - "testing" - - log "github.com/hashicorp/go-hclog" - "github.com/hashicorp/vault/sdk/helper/docker" - "github.com/hashicorp/vault/sdk/helper/logging" - "github.com/hashicorp/vault/sdk/physical" -) - -type Config struct { - docker.ServiceURL - TableName string - HATableName string -} - -var _ docker.ServiceConfig = &Config{} - -func prepareCockroachDBTestContainer(t *testing.T) (func(), *Config) { - if retURL := os.Getenv("CR_URL"); retURL != "" { - s, err := docker.NewServiceURLParse(retURL) - if err != nil { - t.Fatal(err) - } - return func() {}, &Config{ - ServiceURL: *s, - TableName: "vault." + defaultTableName, - HATableName: "vault." + defaultHATableName, - } - } - - runner, err := docker.NewServiceRunner(docker.RunOptions{ - ImageRepo: "docker.mirror.hashicorp.services/cockroachdb/cockroach", - ImageTag: "release-1.0", - ContainerName: "cockroachdb", - Cmd: []string{"start", "--insecure"}, - Ports: []string{"26257/tcp"}, - }) - if err != nil { - t.Fatalf("Could not start docker CockroachDB: %s", err) - } - svc, err := runner.StartService(context.Background(), connectCockroachDB) - if err != nil { - t.Fatalf("Could not start docker CockroachDB: %s", err) - } - - return svc.Cleanup, svc.Config.(*Config) -} - -func connectCockroachDB(ctx context.Context, host string, port int) (docker.ServiceConfig, error) { - u := url.URL{ - Scheme: "postgresql", - User: url.UserPassword("root", ""), - Host: fmt.Sprintf("%s:%d", host, port), - RawQuery: "sslmode=disable", - } - - db, err := sql.Open("pgx", u.String()) - if err != nil { - return nil, err - } - defer db.Close() - - database := "vault" - _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", database)) - if err != nil { - return nil, err - } - - return &Config{ - ServiceURL: *docker.NewServiceURL(u), - TableName: database + "." + defaultTableName, - HATableName: database + "." + defaultHATableName, - }, nil -} - -func TestCockroachDBBackend(t *testing.T) { - cleanup, config := prepareCockroachDBTestContainer(t) - defer cleanup() - - hae := os.Getenv("CR_HA_ENABLED") - if hae == "" { - hae = "true" - } - - // Run vault tests - logger := logging.NewVaultLogger(log.Debug) - - b1, err := NewCockroachDBBackend(map[string]string{ - "connection_url": config.URL().String(), - "table": config.TableName, - "ha_table": config.HATableName, - "ha_enabled": hae, - }, logger) - if err != nil { - t.Fatalf("Failed to create new backend: %v", err) - } - - b2, err := NewCockroachDBBackend(map[string]string{ - "connection_url": config.URL().String(), - "table": config.TableName, - "ha_table": config.HATableName, - "ha_enabled": hae, - }, logger) - if err != nil { - t.Fatalf("Failed to create new backend: %v", err) - } - - defer func() { - truncate(t, b1) - truncate(t, b2) - }() - - physical.ExerciseBackend(t, b1) - truncate(t, b1) - physical.ExerciseBackend_ListPrefix(t, b1) - truncate(t, b1) - physical.ExerciseTransactionalBackend(t, b1) - truncate(t, b1) - - ha1, ok1 := b1.(physical.HABackend) - ha2, ok2 := b2.(physical.HABackend) - if !ok1 || !ok2 { - t.Fatalf("CockroachDB does not implement HABackend") - } - - if ha1.HAEnabled() && ha2.HAEnabled() { - logger.Info("Running ha backend tests") - physical.ExerciseHABackend(t, ha1, ha2) - } -} - -func truncate(t *testing.T, b physical.Backend) { - crdb := b.(*CockroachDBBackend) - _, err := crdb.client.Exec("TRUNCATE TABLE " + crdb.table) - if err != nil { - t.Fatalf("Failed to drop table: %v", err) - } - if crdb.haEnabled { - _, err = crdb.client.Exec("TRUNCATE TABLE " + crdb.haTable) - if err != nil { - t.Fatalf("Failed to drop table: %v", err) - } - } -} - -func TestValidateDBTable(t *testing.T) { - type testCase struct { - table string - expectErr bool - } - - tests := map[string]testCase{ - "first character is letter": {"abcdef", false}, - "first character is underscore": {"_bcdef", false}, - "exclamation point": {"ab!def", true}, - "at symbol": {"ab@def", true}, - "hash": {"ab#def", true}, - "percent": {"ab%def", true}, - "carrot": {"ab^def", true}, - "ampersand": {"ab&def", true}, - "star": {"ab*def", true}, - "left paren": {"ab(def", true}, - "right paren": {"ab)def", true}, - "dash": {"ab-def", true}, - "digit": {"a123ef", false}, - "dollar end": {"abcde$", false}, - "dollar middle": {"ab$def", false}, - "dollar start": {"$bcdef", true}, - "backtick prefix": {"`bcdef", true}, - "backtick middle": {"ab`def", true}, - "backtick suffix": {"abcde`", true}, - "single quote prefix": {"'bcdef", true}, - "single quote middle": {"ab'def", true}, - "single quote suffix": {"abcde'", true}, - "double quote prefix": {`"bcdef`, true}, - "double quote middle": {`ab"def`, true}, - "double quote suffix": {`abcde"`, true}, - "underscore with all runes": {"_bcd123__a__$", false}, - "all runes": {"abcd123__a__$", false}, - "default table name": {defaultTableName, false}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - err := validateDBTable(test.table) - if test.expectErr && err == nil { - t.Fatalf("err expected, got nil") - } - if !test.expectErr && err != nil { - t.Fatalf("no error expected, got: %s", err) - } - }) - t.Run(fmt.Sprintf("database: %s", name), func(t *testing.T) { - dbTable := fmt.Sprintf("%s.%s", test.table, test.table) - err := validateDBTable(dbTable) - if test.expectErr && err == nil { - t.Fatalf("err expected, got nil") - } - if !test.expectErr && err != nil { - t.Fatalf("no error expected, got: %s", err) - } - }) - } -} diff --git a/physical/cockroachdb/keywords.go b/physical/cockroachdb/keywords.go deleted file mode 100644 index c57c9eae1..000000000 --- a/physical/cockroachdb/keywords.go +++ /dev/null @@ -1,441 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package cockroachdb - -// sqlKeywords is a reference of all of the keywords that we do not allow for use as the table name -// Referenced from: -// https://www.cockroachlabs.com/docs/stable/keywords-and-identifiers.html#identifiers -// -> https://www.cockroachlabs.com/docs/stable/keywords-and-identifiers.html#keywords -// -> https://www.cockroachlabs.com/docs/stable/sql-grammar.html -var sqlKeywords = map[string]bool{ - // reserved_keyword - // https://www.cockroachlabs.com/docs/stable/sql-grammar.html#reserved_keyword - "ALL": true, - "ANALYSE": true, - "ANALYZE": true, - "AND": true, - "ANY": true, - "ARRAY": true, - "AS": true, - "ASC": true, - "ASYMMETRIC": true, - "BOTH": true, - "CASE": true, - "CAST": true, - "CHECK": true, - "COLLATE": true, - "COLUMN": true, - "CONCURRENTLY": true, - "CONSTRAINT": true, - "CREATE": true, - "CURRENT_CATALOG": true, - "CURRENT_DATE": true, - "CURRENT_ROLE": true, - "CURRENT_SCHEMA": true, - "CURRENT_TIME": true, - "CURRENT_TIMESTAMP": true, - "CURRENT_USER": true, - "DEFAULT": true, - "DEFERRABLE": true, - "DESC": true, - "DISTINCT": true, - "DO": true, - "ELSE": true, - "END": true, - "EXCEPT": true, - "FALSE": true, - "FETCH": true, - "FOR": true, - "FOREIGN": true, - "FROM": true, - "GRANT": true, - "GROUP": true, - "HAVING": true, - "IN": true, - "INITIALLY": true, - "INTERSECT": true, - "INTO": true, - "LATERAL": true, - "LEADING": true, - "LIMIT": true, - "LOCALTIME": true, - "LOCALTIMESTAMP": true, - "NOT": true, - "NULL": true, - "OFFSET": true, - "ON": true, - "ONLY": true, - "OR": true, - "ORDER": true, - "PLACING": true, - "PRIMARY": true, - "REFERENCES": true, - "RETURNING": true, - "SELECT": true, - "SESSION_USER": true, - "SOME": true, - "SYMMETRIC": true, - "TABLE": true, - "THEN": true, - "TO": true, - "TRAILING": true, - "TRUE": true, - "UNION": true, - "UNIQUE": true, - "USER": true, - "USING": true, - "VARIADIC": true, - "WHEN": true, - "WHERE": true, - "WINDOW": true, - "WITH": true, - - // cockroachdb_extra_reserved_keyword - // https://www.cockroachlabs.com/docs/stable/sql-grammar.html#cockroachdb_extra_reserved_keyword - "INDEX": true, - "NOTHING": true, - - // type_func_name_keyword - // https://www.cockroachlabs.com/docs/stable/sql-grammar.html#type_func_name_keyword - "COLLATION": true, - "CROSS": true, - "FULL": true, - "INNER": true, - "ILIKE": true, - "IS": true, - "ISNULL": true, - "JOIN": true, - "LEFT": true, - "LIKE": true, - "NATURAL": true, - "NONE": true, - "NOTNULL": true, - "OUTER": true, - "OVERLAPS": true, - "RIGHT": true, - "SIMILAR": true, - "FAMILY": true, - - // col_name_keyword - // https://www.cockroachlabs.com/docs/stable/sql-grammar.html#col_name_keyword - "ANNOTATE_TYPE": true, - "BETWEEN": true, - "BIGINT": true, - "BIT": true, - "BOOLEAN": true, - "CHAR": true, - "CHARACTER": true, - "CHARACTERISTICS": true, - "COALESCE": true, - "DEC": true, - "DECIMAL": true, - "EXISTS": true, - "EXTRACT": true, - "EXTRACT_DURATION": true, - "FLOAT": true, - "GREATEST": true, - "GROUPING": true, - "IF": true, - "IFERROR": true, - "IFNULL": true, - "INT": true, - "INTEGER": true, - "INTERVAL": true, - "ISERROR": true, - "LEAST": true, - "NULLIF": true, - "NUMERIC": true, - "OUT": true, - "OVERLAY": true, - "POSITION": true, - "PRECISION": true, - "REAL": true, - "ROW": true, - "SMALLINT": true, - "SUBSTRING": true, - "TIME": true, - "TIMETZ": true, - "TIMESTAMP": true, - "TIMESTAMPTZ": true, - "TREAT": true, - "TRIM": true, - "VALUES": true, - "VARBIT": true, - "VARCHAR": true, - "VIRTUAL": true, - "WORK": true, - - // unreserved_keyword - // https://www.cockroachlabs.com/docs/stable/sql-grammar.html#unreserved_keyword - "ABORT": true, - "ACTION": true, - "ADD": true, - "ADMIN": true, - "AGGREGATE": true, - "ALTER": true, - "AT": true, - "AUTOMATIC": true, - "AUTHORIZATION": true, - "BACKUP": true, - "BEGIN": true, - "BIGSERIAL": true, - "BLOB": true, - "BOOL": true, - "BUCKET_COUNT": true, - "BUNDLE": true, - "BY": true, - "BYTEA": true, - "BYTES": true, - "CACHE": true, - "CANCEL": true, - "CASCADE": true, - "CHANGEFEED": true, - "CLUSTER": true, - "COLUMNS": true, - "COMMENT": true, - "COMMIT": true, - "COMMITTED": true, - "COMPACT": true, - "COMPLETE": true, - "CONFLICT": true, - "CONFIGURATION": true, - "CONFIGURATIONS": true, - "CONFIGURE": true, - "CONSTRAINTS": true, - "CONVERSION": true, - "COPY": true, - "COVERING": true, - "CREATEROLE": true, - "CUBE": true, - "CURRENT": true, - "CYCLE": true, - "DATA": true, - "DATABASE": true, - "DATABASES": true, - "DATE": true, - "DAY": true, - "DEALLOCATE": true, - "DELETE": true, - "DEFERRED": true, - "DISCARD": true, - "DOMAIN": true, - "DOUBLE": true, - "DROP": true, - "ENCODING": true, - "ENUM": true, - "ESCAPE": true, - "EXCLUDE": true, - "EXECUTE": true, - "EXPERIMENTAL": true, - "EXPERIMENTAL_AUDIT": true, - "EXPERIMENTAL_FINGERPRINTS": true, - "EXPERIMENTAL_RELOCATE": true, - "EXPERIMENTAL_REPLICA": true, - "EXPIRATION": true, - "EXPLAIN": true, - "EXPORT": true, - "EXTENSION": true, - "FILES": true, - "FILTER": true, - "FIRST": true, - "FLOAT4": true, - "FLOAT8": true, - "FOLLOWING": true, - "FORCE_INDEX": true, - "FUNCTION": true, - "GLOBAL": true, - "GRANTS": true, - "GROUPS": true, - "HASH": true, - "HIGH": true, - "HISTOGRAM": true, - "HOUR": true, - "IMMEDIATE": true, - "IMPORT": true, - "INCLUDE": true, - "INCREMENT": true, - "INCREMENTAL": true, - "INDEXES": true, - "INET": true, - "INJECT": true, - "INSERT": true, - "INT2": true, - "INT2VECTOR": true, - "INT4": true, - "INT8": true, - "INT64": true, - "INTERLEAVE": true, - "INVERTED": true, - "ISOLATION": true, - "JOB": true, - "JOBS": true, - "JSON": true, - "JSONB": true, - "KEY": true, - "KEYS": true, - "KV": true, - "LANGUAGE": true, - "LAST": true, - "LC_COLLATE": true, - "LC_CTYPE": true, - "LEASE": true, - "LESS": true, - "LEVEL": true, - "LIST": true, - "LOCAL": true, - "LOCKED": true, - "LOGIN": true, - "LOOKUP": true, - "LOW": true, - "MATCH": true, - "MATERIALIZED": true, - "MAXVALUE": true, - "MERGE": true, - "MINUTE": true, - "MINVALUE": true, - "MONTH": true, - "NAMES": true, - "NAN": true, - "NAME": true, - "NEXT": true, - "NO": true, - "NORMAL": true, - "NO_INDEX_JOIN": true, - "NOCREATEROLE": true, - "NOLOGIN": true, - "NOWAIT": true, - "NULLS": true, - "IGNORE_FOREIGN_KEYS": true, - "OF": true, - "OFF": true, - "OID": true, - "OIDS": true, - "OIDVECTOR": true, - "OPERATOR": true, - "OPT": true, - "OPTION": true, - "OPTIONS": true, - "ORDINALITY": true, - "OTHERS": true, - "OVER": true, - "OWNED": true, - "PARENT": true, - "PARTIAL": true, - "PARTITION": true, - "PARTITIONS": true, - "PASSWORD": true, - "PAUSE": true, - "PHYSICAL": true, - "PLAN": true, - "PLANS": true, - "PRECEDING": true, - "PREPARE": true, - "PRESERVE": true, - "PRIORITY": true, - "PUBLIC": true, - "PUBLICATION": true, - "QUERIES": true, - "QUERY": true, - "RANGE": true, - "RANGES": true, - "READ": true, - "RECURSIVE": true, - "REF": true, - "REGCLASS": true, - "REGPROC": true, - "REGPROCEDURE": true, - "REGNAMESPACE": true, - "REGTYPE": true, - "REINDEX": true, - "RELEASE": true, - "RENAME": true, - "REPEATABLE": true, - "REPLACE": true, - "RESET": true, - "RESTORE": true, - "RESTRICT": true, - "RESUME": true, - "REVOKE": true, - "ROLE": true, - "ROLES": true, - "ROLLBACK": true, - "ROLLUP": true, - "ROWS": true, - "RULE": true, - "SETTING": true, - "SETTINGS": true, - "STATUS": true, - "SAVEPOINT": true, - "SCATTER": true, - "SCHEMA": true, - "SCHEMAS": true, - "SCRUB": true, - "SEARCH": true, - "SECOND": true, - "SERIAL": true, - "SERIALIZABLE": true, - "SERIAL2": true, - "SERIAL4": true, - "SERIAL8": true, - "SEQUENCE": true, - "SEQUENCES": true, - "SERVER": true, - "SESSION": true, - "SESSIONS": true, - "SET": true, - "SHARE": true, - "SHOW": true, - "SIMPLE": true, - "SKIP": true, - "SMALLSERIAL": true, - "SNAPSHOT": true, - "SPLIT": true, - "SQL": true, - "START": true, - "STATISTICS": true, - "STDIN": true, - "STORE": true, - "STORED": true, - "STORING": true, - "STRICT": true, - "STRING": true, - "SUBSCRIPTION": true, - "SYNTAX": true, - "SYSTEM": true, - "TABLES": true, - "TEMP": true, - "TEMPLATE": true, - "TEMPORARY": true, - "TESTING_RELOCATE": true, - "TEXT": true, - "TIES": true, - "TRACE": true, - "TRANSACTION": true, - "TRIGGER": true, - "TRUNCATE": true, - "TRUSTED": true, - "TYPE": true, - "THROTTLING": true, - "UNBOUNDED": true, - "UNCOMMITTED": true, - "UNKNOWN": true, - "UNLOGGED": true, - "UNSPLIT": true, - "UNTIL": true, - "UPDATE": true, - "UPSERT": true, - "UUID": true, - "USE": true, - "USERS": true, - "VALID": true, - "VALIDATE": true, - "VALUE": true, - "VARYING": true, - "VIEW": true, - "WITHIN": true, - "WITHOUT": true, - "WRITE": true, - "YEAR": true, - "ZONE": true, -}