Skip to content

Commit

Permalink
sql: drop the attachdriver mechanism (#97)
Browse files Browse the repository at this point in the history
After security assessment it was determined storing filterable/sortable
columns on disk is acceptable, as none of the information is expected
to be sensible.

This removes the attachdriver mechanism that created one DB on the disk
and another, optionally in memory, as attached DB.

Only one DB is created now.

This change was driven by the need of dropping the shared cache SQLite
mode:

https://sqlite.org/sharedcache.html

which is in turn needed in order to activate WAL mode, which allows
for better performance and less locking (including at CREATE INDEX
time).

Signed-off-by: Silvio Moioli <[email protected]>
Co-authored-by: Paulo Gomes <[email protected]>
  • Loading branch information
moio and pjbgf authored Sep 10, 2024
1 parent e298e0c commit 591e606
Show file tree
Hide file tree
Showing 7 changed files with 38 additions and 178 deletions.
18 changes: 2 additions & 16 deletions pkg/cache/sql/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
- [How to Use](#how-to-use)
- [Technical Information](#technical-information)
- [SQL Tables](#sql-tables)
- [Attach Driver](#attach-driver)
- [SQLite Driver](#sqlite-driver)
- [Connection Pooling](#connection-pooling)
- [Encryption Defaults](#encryption-defaults)
Expand Down Expand Up @@ -103,24 +102,13 @@ that it is desired to filter or order on.
* indices table - the indices table stores indexes created and objects' values for each index. This backs the generic indexer
that contains the functionality needed to conform to cache.Indexer.

### Attach Driver
The fields tables may contain sensitive information, but because we need to use its values for indexing it cannot be encrypted.
The attach driver automatically attaches a second database on every real sqlite connection. The database is able to exist
in memory while the primary object database in on-disk. The attach allows queries to be made to either in a single connection.
An example of a query using both databases:
```sqlite
JOIN db2."secrets_fields" f ON o.key = f.key
```

### SQLite Driver
There are multiple SQLite drivers that this package could have used. One of the most, if not the most, popular SQLite golang
drivers is [mattn/go-sqlite3](https://github.com/mattn/go-sqlite3). This driver is not being used because it requires enabling
the cgo option when compiling and at the moment lasso's main consumer, rancher, does not compile with cgo. We did not want
the SQL informer to be the sole driver in switching to using cgo. Instead, modernc's driver which is in pure golang. Side-by-side
comparisons can be found indicating the cgo version is, as expected, more performant. If in the future it is deemed worthwhile
then the driver can be easily switched following these steps:
1. Replace empty import in `pkg/cache/sql/store`. Change `_ "modernc.org/sqlite"` to `_ "github.com/mattn/go-sqlite3"`.
2. In `attachdriver` package, register `SQLDriver` struct from `mattn/gosqlite3` instead of `Driver` from `modernc.org/sqlite`.
then the driver can be easily switched by replacing the empty import in `pkg/cache/sql/store` from `_ "modernc.org/sqlite"` to `_ "github.com/mattn/go-sqlite3"`.

### Connection Pooling
While working with the `database/sql` package for go, it is important to understand how sql.Open() and other methods manage
Expand Down Expand Up @@ -166,6 +154,4 @@ of a query that may be user supplied, such as columns, should be carefully valid

### Troubleshooting SQLite
A useful tool for troubleshooting the database files is the sqlite command line tool. Another useful tool is the goland
sqlite plugin. Both of these tools can be used with the database files. If running a database in-memory, there is no option
for navigating records. In-memory databases are used for the non-object database when `CATTLE_ENCRYPT_CACHE_ALL` is set to
"true".
sqlite plugin. Both of these tools can be used with the database files.
64 changes: 0 additions & 64 deletions pkg/cache/sql/attachdriver/driver.go

This file was deleted.

45 changes: 0 additions & 45 deletions pkg/cache/sql/attachdriver/driver_test.go

This file was deleted.

10 changes: 1 addition & 9 deletions pkg/cache/sql/db/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"time"

"github.com/pkg/errors"
"github.com/rancher/lasso/pkg/cache/sql/attachdriver"
"github.com/rancher/lasso/pkg/cache/sql/db/transaction"
"k8s.io/apimachinery/pkg/util/wait"
"modernc.org/sqlite"
Expand All @@ -26,9 +25,6 @@ import (
const (
// InformerObjectCacheDBPath is where SQLite's object database file will be stored relative to process running lasso
InformerObjectCacheDBPath = "informer_object_cache.db"
// OnDiskInformerIndexedFieldDBPath is where SQLite's indexed fields database file will be stored if, env var value
// found at EncryptAllEnvVar is "false".
OnDiskInformerIndexedFieldDBPath = "informer_object_fields.db"
)

// Client is a database client that provides encrypting, decrypting, and database resetting.
Expand Down Expand Up @@ -336,11 +332,7 @@ func (c *Client) NewConnection() error {
return err
}

err = os.RemoveAll(OnDiskInformerIndexedFieldDBPath)
if err != nil {
return err
}
sqlDB, err := sql.Open(attachdriver.Name, "file:"+InformerObjectCacheDBPath+"?mode=rwc&cache=shared&_journal_mode=wal&_synchronous=off&_foreign_keys=on&_busy_timeout=1000000")
sqlDB, err := sql.Open("sqlite", "file:"+InformerObjectCacheDBPath+"?mode=rwc&cache=shared&_journal_mode=wal&_synchronous=off&_foreign_keys=on&_busy_timeout=1000000")
if err != nil {
return err
}
Expand Down
17 changes: 4 additions & 13 deletions pkg/cache/sql/informer/factory/informer_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/rancher/lasso/pkg/cache/sql/informer"

"github.com/rancher/lasso/pkg/cache/sql/attachdriver"
"github.com/rancher/lasso/pkg/cache/sql/db"
"github.com/rancher/lasso/pkg/cache/sql/encryption"
sqlStore "github.com/rancher/lasso/pkg/cache/sql/store"
Expand All @@ -20,6 +19,10 @@ import (
"k8s.io/client-go/tools/cache"
)

// EncryptAllEnvVar is set to "true" if users want all types' data blobs to be encrypted in SQLite
// otherwise only variables in defaultEncryptedResourceTypes will have their blobs encrypted
const EncryptAllEnvVar = "CATTLE_ENCRYPT_CACHE_ALL"

// CacheFactory builds Informer instances and keeps a cache of instances it created
type CacheFactory struct {
informerCreateLock sync.RWMutex
Expand Down Expand Up @@ -56,18 +59,6 @@ var defaultEncryptedResourceTypes = map[schema.GroupVersionKind]struct{}{
}: {},
}

const (
EncryptAllEnvVar = "CATTLE_ENCRYPT_CACHE_ALL"
)

func init() {
indexedFieldDBPath := db.OnDiskInformerIndexedFieldDBPath
if os.Getenv(EncryptAllEnvVar) == "true" {
indexedFieldDBPath = ":memory:"
}
attachdriver.Register("file:" + indexedFieldDBPath + "?cache=shared")
}

// NewCacheFactory returns an informer factory instance
func NewCacheFactory() (*CacheFactory, error) {
m, err := encryption.NewManager()
Expand Down
10 changes: 5 additions & 5 deletions pkg/cache/sql/informer/listoption_indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ var (
const (
matchFmt = `%%%s%%`
strictMatchFmt = `%s`
createFieldsTableFmt = `CREATE TABLE db2."%s_fields" (
createFieldsTableFmt = `CREATE TABLE "%s_fields" (
key TEXT NOT NULL PRIMARY KEY,
%s
)`
createFieldsIndexFmt = `CREATE INDEX db2."%s_%s_index" ON "%s_fields"("%s")`
createFieldsIndexFmt = `CREATE INDEX "%s_%s_index" ON "%s_fields"("%s")`

failedToGetFromSliceFmt = "[listoption indexer] failed to get subfield [%s] from slice items: %w"
)
Expand Down Expand Up @@ -127,13 +127,13 @@ func NewListOptionIndexer(fields [][]string, s Store, namespaced bool) (*ListOpt
}

l.addFieldQuery = fmt.Sprintf(
`INSERT INTO db2."%s_fields"(key, %s) VALUES (?, %s) ON CONFLICT DO UPDATE SET %s`,
`INSERT INTO "%s_fields"(key, %s) VALUES (?, %s) ON CONFLICT DO UPDATE SET %s`,
db.Sanitize(i.GetName()),
strings.Join(columns, ", "),
strings.Join(qmarks, ", "),
strings.Join(setStatements, ", "),
)
l.deleteFieldQuery = fmt.Sprintf(`DELETE FROM db2."%s_fields" WHERE key = ?`, db.Sanitize(i.GetName()))
l.deleteFieldQuery = fmt.Sprintf(`DELETE FROM "%s_fields" WHERE key = ?`, db.Sanitize(i.GetName()))

l.addFieldStmt = l.Prepare(l.addFieldQuery)
l.deleteFieldStmt = l.Prepare(l.deleteFieldQuery)
Expand Down Expand Up @@ -195,7 +195,7 @@ func (l *ListOptionIndexer) ListByOptions(ctx context.Context, lo ListOptions, p
// 1- Intro: SELECT and JOIN clauses
query := fmt.Sprintf(`SELECT o.object, o.objectnonce, o.dekid FROM "%s" o`, db.Sanitize(l.GetName()))
query += "\n "
query += fmt.Sprintf(`JOIN db2."%s_fields" f ON o.key = f.key`, db.Sanitize(l.GetName()))
query += fmt.Sprintf(`JOIN "%s_fields" f ON o.key = f.key`, db.Sanitize(l.GetName()))
params := []any{}

// 2- Filtering: WHERE clauses (from lo.Filters)
Expand Down
Loading

0 comments on commit 591e606

Please sign in to comment.