Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Encryption At Rest #284

Merged
merged 6 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,28 @@ curl -H "Authorization: Bearer ${JWT_TOKEN_HERE}" http://localhost:5984/_session
```bash
ng run fastenhealth:storybook
```


# Access Encrypted SQLite Database with IntelliJ

- Download the latest `sqlite-jdbc-crypt` jar from https://github.com/Willena/sqlite-jdbc-crypt/releases
- Open IntelliJ -> Data Source Properties -> Driver Tab
- Find & Select `Sqlite` -> Right Click -> Duplicate
- Rename to `Sqlite (Encrypted)`
- Find `Driver Files` -> Select `sqlite-jdbc-crypt` jar that you downloaded previously
- Remove `Xerial Sqlite JDBC` jar
- Click `Apply` -> Click `OK`
- Create New Data Source -> Select `Sqlite (Encrypted)` -> Change Connection Type to `Url only`
- Specify the following connection url: `jdbc:sqlite:fasten.db?cipher=sqlcipher&legacy=3&hmac_use=0&kdf_iter=4000&legacy_page_size=1024&key=123456789012345678901234567890`
- Replace `key` with the encryption key specified in your config file (`database.encryption_key`)
- Click `Test Connection` -> Should be successful
- Click `Apply` -> Click `OK`

# Flush SQLite Write-Ahead-Log (WAL) to Database

```sqlite
PRAGMA wal_checkpoint(TRUNCATE);
```

See: https://sqlite.org/forum/info/fefd56014e2135589ea57825b0e2aa3e2df5daf53b5e41aa6a9d8f0c29d0b8e5
TODO: check if https://www.sqlite.org/pragma.html#pragma_wal_checkpoint can be used to do this automatically.
11 changes: 10 additions & 1 deletion backend/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (c *configuration) Init() error {
c.SetDefault("web.src.frontend.path", "/opt/fasten/web")
c.SetDefault("database.type", "sqlite")
c.SetDefault("database.location", "/opt/fasten/db/fasten.db")
//c.SetDefault("database.encryption.key", "") //encryption key must be set by the user.
c.SetDefault("cache.location", "/opt/fasten/cache/")

c.SetDefault("jwt.issuer.key", "thisismysupersecuressessionsecretlength")
Expand Down Expand Up @@ -80,6 +81,14 @@ func (c *configuration) ReadConfig(configFilePath string) error {

// This function ensures that required configuration keys (that must be manually set) are present
func (c *configuration) ValidateConfig() error {

if c.IsSet("database.encryption.key") {
key := c.GetString("database.encryption.key")
if key == "" {
return errors.ConfigValidationError("database.encryption.key cannot be empty")
}
if len(key) < 10 {
return errors.ConfigValidationError("database.encryption.key must be at least 10 characters")
}
}
return nil
}
25 changes: 25 additions & 0 deletions backend/pkg/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package config

import (
"github.com/fastenhealth/fasten-onprem/backend/pkg/errors"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"testing"
)

func Test_ValidateConfig(t *testing.T) {
//setup
testConfig := configuration{
Viper: viper.New(),
}

//test & verify
testConfig.Set("database.encryption.key", "tooshort")
err := testConfig.ValidateConfig()
require.ErrorIs(t, err, errors.ConfigValidationError("database.encryption.key must be at least 10 characters"))

testConfig.Set("database.encryption.key", "")
err = testConfig.ValidateConfig()
require.ErrorIs(t, err, errors.ConfigValidationError("database.encryption.key cannot be empty"))

}
21 changes: 5 additions & 16 deletions backend/pkg/database/gorm_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strings"
"time"

Expand Down Expand Up @@ -485,7 +484,7 @@ func (gr *GormRepository) AddResourceAssociation(ctx context.Context, source *mo
"related_resource_source_resource_type": relatedResourceType,
"related_resource_source_resource_id": relatedResourceId,
}).Error
uniqueConstraintError := errors.New("constraint failed: UNIQUE constraint failed")
uniqueConstraintError := errors.New("UNIQUE constraint failed")
if err != nil {
if strings.HasPrefix(err.Error(), uniqueConstraintError.Error()) {
gr.Logger.Warnf("Ignoring an error when creating a related_resource association for %s/%s: %v", resourceType, resourceId, err)
Expand Down Expand Up @@ -1002,8 +1001,11 @@ func (gr *GormRepository) ListBackgroundJobs(ctx context.Context, queryOptions m
var backgroundJobs []models.BackgroundJob
query := gr.GormClient.WithContext(ctx).
//Group("source_id"). //broken in Postgres.
Where(queryParam).Limit(queryOptions.Limit).Order("locked_time DESC")
Where(queryParam).Order("locked_time DESC")

if queryOptions.Limit > 0 {
query = query.Limit(queryOptions.Limit)
}
if queryOptions.Offset > 0 {
query = query.Offset(queryOptions.Offset)
}
Expand Down Expand Up @@ -1119,19 +1121,6 @@ func (gr *GormRepository) CancelAllLockedBackgroundJobsAndFail() error {
// Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

func sqlitePragmaString(pragmas map[string]string) string {
q := url.Values{}
for key, val := range pragmas {
q.Add("_pragma", fmt.Sprintf("%s=%s", key, val))
}

queryStr := q.Encode()
if len(queryStr) > 0 {
return "?" + queryStr
}
return ""
}

// Internal function
// This function will return a list of resources from all FHIR tables in the database
// The query allows us to set the source id, source resource id, source resource type
Expand Down
3 changes: 3 additions & 0 deletions backend/pkg/database/gorm_repository_query_sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (suite *RepositorySqlTestSuite) BeforeTest(suiteName, testName string) {
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("database.type").Return("sqlite").AnyTimes()
fakeConfig.EXPECT().IsSet("database.encryption.key").Return(false).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()), event_bus.NewNoopEventBusServer())
require.NoError(suite.T(), err)
Expand All @@ -62,6 +63,8 @@ func (suite *RepositorySqlTestSuite) BeforeTest(suiteName, testName string) {
func (suite *RepositorySqlTestSuite) AfterTest(suiteName, testName string) {
suite.MockCtrl.Finish()
os.Remove(suite.TestDatabase.Name())
os.Remove(suite.TestDatabase.Name() + "-shm")
os.Remove(suite.TestDatabase.Name() + "-wal")
}

// In order for 'go test' to run this suite, we need to create
Expand Down
1 change: 1 addition & 0 deletions backend/pkg/database/gorm_repository_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ func (suite *RepositoryTestSuite) TestQueryResources_SQL() {
fakeConfig := mock_config.NewMockInterface(suite.MockCtrl)
fakeConfig.EXPECT().GetString("database.location").Return(suite.TestDatabase.Name()).AnyTimes()
fakeConfig.EXPECT().GetString("database.type").Return("sqlite").AnyTimes()
fakeConfig.EXPECT().IsSet("database.encryption.key").Return(false).AnyTimes()
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()), event_bus.NewNoopEventBusServer())
require.NoError(suite.T(), err)
Expand Down
Loading
Loading