Skip to content

Commit 210447e

Browse files
feat: Enforcing Database Encryption & Enable HTTPS by default (#603)
* add placeholder stylus version * FO-33 - First version encryption * refactor: Refactor check token logic on FE and BE [FO-33] * FO-33 - Run server without token * FO-33 - Optimization design for setup-token page * FO-33 - First change * FO-33 - Resolve undefined appConfig in handlers * FO-33 - Remove encrypt_db from .gitignore * FO-33 - Use literal string for encryption token * FO-33 - Optimization server and handler * FO-33-SSL - add ssl in dockerfile and app * feat: Improve token wizard flow, add get token component and revamp restore token UI [FO-33] * feat: Add redirect to main page button after token is set [FO-33] * FO-33 - optimization run server * feat: Adjust generate token flow, add mandatory download [FO-33] * FO-33 - Validation token connect db * feat: Improve validate token flow, add test token functionality [FO-33] * FO-33 - fix validate token * FO-33 - optimization when token is wrong * FO-33-SSL - Remove mkcert from dockerfile * FO-33-SSL - ignore only crt and key * chore: remove unused var * chore: remove unused volumes * chore: rename var name * chore: rename funct * chore: refactor encryption-key related components and handlers * chore: revert stylus change * chore: remove unused member * chore: revert exportable field * chore: remove running server in a loop. making appEngine reinitialize logic self-contained * misc: cleanup * misc: cleanup * misc: cleanup * misc: cleanup * chore: adjust tests * chore: rename encryption packege to regular random * chore: rename encryption_key * chore: add opt-out for database encryption option * chore: revert useless changes * chore: refactor encryption-status-guard * chore: cleanup * chore: cleanup * chore: cleanup * chore: cleanup * chore: fix config test * chore: send encryption key as json * fix: resolve client routing issue * fix: bug on updating the state on encryption-key/wizard-restore * fix: adjust backend config validation * chore: revert config tests * misc: cleanup * chore: adjust backend tests * chore: fix backend test * chore: refactor encryption-key/wizard-restore flow * chore: adjust the component with a minimalistic test spec * chore: adjust the look of the get-encryption-key-wizard component * chore: change the look of get-encryption-key-wizard component * chore: make default config database.encryption false * chore: revert Dockerfile changes * chore: revert useless changes * chore: add auto-gen certificates when server runs with https option on * chore: adjust ng-cli dev mode options * chore: adjust ng-cli dev options * chore: mount shared certs folder in docker compose * docs: update launching docs * chore: put rootCA-key.pem into shared dir * fix: adjust get-encryption-key component * chore: define clear stanby flag for the server. refactor frontend flow arround the flag * fix: clean broken deps * fix: log messages * fix: encryption-status guard logic * chore: adjust tls-related info * chore: misc --------- Co-authored-by: Mihai Pop @ TSA <hello@techstackapps.com>
1 parent 30b1cb0 commit 210447e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1546
-168
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,5 @@ fasten.db-wal
8181

8282
backend/resources/related_versions.json
8383
frontend/documentation.json
84+
85+
certs/

README.md

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,66 @@ If you prefer not to run the `set_env.sh` script, you can configure the `.env` f
140140
PORT=9090
141141
```
142142

143+
Next, open a browser to `https://localhost:9090`
144+
145+
### <a name="using-https"></a>🔒 Using HTTPS and Trusting the Self-Signed Certificate
146+
147+
By default, Fasten On-Prem runs with HTTPS enabled to ensure your data is secure. It uses a self-signed **TLS** certificate, which offers the same level of encryption as a commercially issued certificate. The first time you connect, your browser will display a security warning because it doesn't yet trust the certificate's issuer. The steps below will guide you through the simple, one-time process of telling your browser to trust the certificate, ensuring a secure connection without future warnings. Please note that the generated certificates can be replaced at any time with your own valid TLS certificates.
148+
149+
#### How it Works: The Chain of Trust
150+
151+
To establish a secure connection, your browser needs to trust the server's TLS certificate. Here’s how the process works in Fasten On-Prem:
152+
153+
1. **Root Certificate Authority (CA):** When the application first starts, it generates its own self-contained Certificate Authority, called `"Fasten Health CA"`. Think of this as the highest level of trust. The public part of this CA is the `rootCA.pem` file.
154+
2. **Server Certificate:** The application then uses the `"Fasten Health CA"` to issue and sign a specific certificate for the web server (e.g., for `localhost`).
155+
3. **Browser Verification:** When you connect to the server, it presents the server certificate to your browser. Your browser checks who signed it and sees it was `"Fasten Health CA"`. The browser then asks, "Do I trust the 'Fasten Health CA'?"
156+
157+
Initially, the answer is no, which is why you see a security warning. By following the steps below to import the `rootCA.pem` file, you are telling your browser or operating system to trust our self-generated CA. Once the CA is trusted, any certificates it signs—including the server certificate—will also be trusted, and the connection will be secure without any warnings.
158+
159+
#### 1. Locate the Root CA Certificate
160+
161+
When you run the application using the production Docker Compose file (`docker-compose-prod.yml`), it automatically generates a `rootCA.pem` file. This file is located in the `certs` directory on your host machine.
162+
163+
- **Certificate Path:** `certs/rootCA.pem`
164+
165+
#### 2. Import the Certificate
166+
167+
You will need to import this certificate into your operating system's or browser's trust store. Here are general instructions for different platforms:
168+
169+
**macOS**
170+
171+
1. Open the **Keychain Access** application.
172+
2. Select the **System** keychain.
173+
3. Go to **File > Import Items** and select the `certs/rootCA.pem` file.
174+
4. Find the "Fasten Health CA" certificate in the list, double-click it, and under the **Trust** section, set "When using this certificate" to **Always Trust**.
175+
176+
**Windows**
177+
178+
1. Double-click the `certs/rootCA.pem` file.
179+
2. Click **Install Certificate...** and choose **Local Machine**.
180+
3. Select **Place all certificates in the following store**, click **Browse**, and choose **Trusted Root Certification Authorities**.
181+
4. Complete the wizard to finish the import process.
182+
183+
**Linux (Ubuntu/Debian)**
184+
185+
1. Copy the certificate to the trusted certificates directory:
186+
```bash
187+
sudo cp certs/rootCA.pem /usr/local/share/ca-certificates/fasten-health-ca.crt
188+
```
189+
2. Update the system's certificate store:
190+
```bash
191+
sudo update-ca-certificates
192+
```
193+
194+
**Firefox**
195+
196+
Firefox has its own trust store. To import the certificate:
197+
198+
1. Go to **Settings > Privacy & Security**.
199+
2. Scroll down to **Certificates** and click **View Certificates...**.
200+
3. In the **Authorities** tab, click **Import...** and select the `certs/rootCA.pem` file.
201+
4. Check the box for **Trust this CA to identify websites** and click **OK**.
202+
143203
### 🧪 Develop
144204

145205
Use local development settings for testing and iteration.
@@ -170,13 +230,11 @@ docker run --rm \
170230
ghcr.io/fastenhealth/fasten-onprem:main
171231
```
172232

173-
Next, open a browser to `http://localhost:9090`
174-
175233
At this point you'll be redirected to the login page.
176234
177235
### Logging In
178236
179-
Before you can use the Fasten BETA, you'll need to [Create an Account](http://localhost:9090/web/auth/signup).
237+
Before you can use the Fasten BETA, you'll need to [Create an Account](https://localhost:9090/web/auth/signup).
180238

181239
It can be as simple as
182240
- **Username:** `testuser`

backend/cmd/fasten/fasten.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ func main() {
7676
Name: "start",
7777
Usage: "Start the fasten server",
7878
Action: func(c *cli.Context) error {
79-
//fmt.Fprintln(c.App.Writer, c.Command.Usage)
8079
if c.IsSet("config") {
8180
err = appconfig.ReadConfig(c.String("config")) // Find and read the config file
8281
if err != nil { // Handle errors reading the config file

backend/pkg/config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ func (c *configuration) Init() error {
2525
c.SetDefault("web.listen.host", "0.0.0.0")
2626
c.SetDefault("web.listen.basepath", "")
2727
c.SetDefault("web.listen.https.enabled", false)
28+
c.SetDefault("web.listen.https.certDir", "certs")
29+
c.SetDefault("web.listen.https.sharedDir", "certs/shared")
2830

2931
// allow unsafe endpoints should never be enabled in Production.
3032
// It enables direct API access to healthcare providers without authentication.
@@ -33,6 +35,7 @@ func (c *configuration) Init() error {
3335
c.SetDefault("web.src.frontend.path", "/opt/fasten/web")
3436
c.SetDefault("database.type", "sqlite")
3537
c.SetDefault("database.location", "/opt/fasten/db/fasten.db")
38+
c.SetDefault("database.encryption.enabled", false)
3639
//c.SetDefault("database.encryption.key", "") //encryption key must be set by the user.
3740
c.SetDefault("cache.location", "/opt/fasten/cache/")
3841

backend/pkg/database/factory.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
func NewRepository(appConfig config.Interface, globalLogger logrus.FieldLogger, eventBus event_bus.Interface) (DatabaseRepository, error) {
1212
switch pkg.DatabaseRepositoryType(appConfig.GetString("database.type")) {
1313
case pkg.DatabaseRepositoryTypeSqlite:
14-
return newSqliteRepository(appConfig, globalLogger, eventBus)
14+
return newSqliteRepository(appConfig, globalLogger, eventBus, appConfig.GetBool("database.validation_mode"))
1515
case pkg.DatabaseRepositoryTypePostgres:
1616
return newPostgresRepository(appConfig, globalLogger, eventBus)
1717
default:

backend/pkg/database/gorm_repository_graph_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ func (suite *RepositoryGraphTestSuite) TestGetFlattenedResourceGraph() {
6262
fakeConfig.EXPECT().GetString("database.type").Return("sqlite").AnyTimes()
6363
fakeConfig.EXPECT().IsSet("database.encryption.key").Return(false).AnyTimes()
6464
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
65+
fakeConfig.EXPECT().GetBool("database.validation_mode").Return(false).AnyTimes()
66+
fakeConfig.EXPECT().GetBool("database.encryption.enabled").Return(false).AnyTimes()
6567
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()), event_bus.NewNoopEventBusServer())
6668
require.NoError(suite.T(), err)
6769

@@ -130,6 +132,8 @@ func (suite *RepositoryGraphTestSuite) TestGetFlattenedResourceGraph_NDJson() {
130132
fakeConfig.EXPECT().GetString("database.type").Return("sqlite").AnyTimes()
131133
fakeConfig.EXPECT().IsSet("database.encryption.key").Return(false).AnyTimes()
132134
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
135+
fakeConfig.EXPECT().GetBool("database.validation_mode").Return(false).AnyTimes()
136+
fakeConfig.EXPECT().GetBool("database.encryption.enabled").Return(false).AnyTimes()
133137
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()), event_bus.NewNoopEventBusServer())
134138
require.NoError(suite.T(), err)
135139

backend/pkg/database/gorm_repository_query_sql_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ func (suite *RepositorySqlTestSuite) BeforeTest(suiteName, testName string) {
4646
fakeConfig.EXPECT().GetString("database.type").Return("sqlite").AnyTimes()
4747
fakeConfig.EXPECT().IsSet("database.encryption.key").Return(false).AnyTimes()
4848
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
49+
fakeConfig.EXPECT().GetBool("database.validation_mode").Return(false).AnyTimes()
50+
fakeConfig.EXPECT().GetBool("database.encryption.enabled").Return(false).AnyTimes()
4951
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()), event_bus.NewNoopEventBusServer())
5052
require.NoError(suite.T(), err)
5153
suite.TestRepository = dbRepo

backend/pkg/database/gorm_repository_query_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ func (suite *RepositoryTestSuite) TestQueryResources_SQL() {
267267
fakeConfig.EXPECT().GetString("database.type").Return("sqlite").AnyTimes()
268268
fakeConfig.EXPECT().IsSet("database.encryption.key").Return(false).AnyTimes()
269269
fakeConfig.EXPECT().GetString("log.level").Return("INFO").AnyTimes()
270+
fakeConfig.EXPECT().GetBool("database.validation_mode").Return(false).AnyTimes()
271+
fakeConfig.EXPECT().GetBool("database.encryption.enabled").Return(false).AnyTimes()
270272
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()), event_bus.NewNoopEventBusServer())
271273
require.NoError(suite.T(), err)
272274
userModel := &models.User{

backend/pkg/database/gorm_repository_related_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ func (suite *RepositoryRelatedTestSuite) SetupTest() {
4242
fakeConfig.EXPECT().GetString("database.type").Return("sqlite").AnyTimes()
4343
fakeConfig.EXPECT().IsSet("database.encryption.key").Return(false).AnyTimes()
4444
fakeConfig.EXPECT().GetString("log.level").Return("DEBUG").AnyTimes()
45+
fakeConfig.EXPECT().GetBool("database.validation_mode").Return(false).AnyTimes()
46+
fakeConfig.EXPECT().GetBool("database.encryption.enabled").Return(false).AnyTimes()
4547

4648
dbRepo, err := NewRepository(fakeConfig, logrus.WithField("test", suite.T().Name()), event_bus.NewNoopEventBusServer())
4749
require.NoError(suite.T(), err, "Failed to create repository")

backend/pkg/database/gorm_repository_settings_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func (suite *RepositorySettingsTestSuite) BeforeTest(suiteName, testName string)
4040
testConfig, err := config.Create()
4141
require.NoError(suite.T(), err)
4242
testConfig.SetDefault("database.location", suite.TestDatabase.Name())
43+
testConfig.SetDefault("database.encryption.enabled", false)
4344
testConfig.SetDefault("log.level", "INFO")
4445
suite.TestConfig = testConfig
4546

0 commit comments

Comments
 (0)