1
0

remove cockroachdb

This commit is contained in:
Konstantin Demin 2024-07-01 12:29:08 +03:00
parent 13230a9749
commit 3fcad1ec13
7 changed files with 0 additions and 1224 deletions

View File

@ -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,

2
go.mod
View File

@ -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

5
go.sum
View File

@ -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=

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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,
}