diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9038b529..3eb949ad 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -2,6 +2,8 @@ name: Build and Release
on:
push:
+ branches:
+ - main
tags:
- 'v*.*.*'
diff --git a/README.md b/README.md
index 8a8ba75b..45060eae 100644
--- a/README.md
+++ b/README.md
@@ -5,22 +5,77 @@ AlertFlow is a monitoring automation platform designed to streamline workflows,
## Table of Contents
- [Features](#features)
-- [Installation](#installation)
-- [Usage](#usage)
+- [Preview](#preview)
+- [Self Hosting](#self-hosting)
+- [Runners](#runners)
- [Project Structure](#project-structure)
+- [Local Development](#local-development)
- [Contributing](#contributing)
- [License](#license)
## Features
-- **Project Management**: Create, edit, and delete projects.
-- **Flows**: Create flows to design workflows per alarm pattern.
-- **Payloads**: Receive incoming alarms from your monitoring infrastructure via payloads & flows.
-- **Runners**: Manage self-hosted runners for executing tasks.
+- **Project Management**: Projects combine a number of Flows and add the option to invite members and control their access.
+- **Flows**: Create flows to design workflows for your incoming alarms.
+- **Payloads**: Receive incoming alarms from your monitoring infrastructure via payloads and automate them via Flows.
+- **Runners**: Runners execute your workflows and can also be self-hosted.
+- **Shared Runners**: Create runners which can be used for all projects.
+- **Scalable to your Needs**: Runners can be scaled with the Auto Join option
- **Team Collaboration**: Invite team members, assign roles, and manage permissions.
-- **Audit Logs**: Track changes and activities within projects.
+- **Audit Logs**: Track changes and activities within projects and flows.
-## Installation
+## Preview
+If you want to checkout AlertFlow before hosting it on your own, have a look at
+https://alertflow.org
+
+Login Details:
+```
+Username: Demo
+Password: demo123
+```
+
+## Self Hosting
+To run your own version of AlertFlow we provide various docker images available at
+[Docker Hub](https://hub.docker.com/repository/docker/justnz/alertflow/general).
+- **justnz/alertflow:latest** - Full version including frontend and backend
+- **justnz/alertflow:vx.x.x** - Versioned release. Also available for the single frontend and backend images
+- **justnz/alertflow:frontend-latest** - Only frontend
+- **justnz/alertflow:backend-latest** - Only backend
+
+### Full Version
+
+Config example: [config.yaml](https://github.com/AlertFlow/alertflow/blob/main/services/backend/config/config.yaml)
+
+```sh
+docker run -p 80:3000 -v /your/config/path/config.yaml:/etc/alertflow/backend_config.yaml justnz/alertflow:latest
+```
+
+### Frontend Only
+If you want to run only the frontend of AlertFlow, please provide the backend endpoint via the below env flag.
+```sh
+docker run -p 80:3000 -e NEXT_PUBLIC_API_URL=https://api-url.com justnz/alertflow:frontend-latest
+```
+
+### Backend Only
+```sh
+docker run -p 8080:8080 -v /your/config/path/config.yaml:/etc/alertflow/backend_config.yaml justnz/alertflow:backend-latest
+```
+
+## Runners
+Runners are the key part of AlertFlow and provide the functionality of execution so called Workflows.
+
+To create / run your own runners you require to have a fully set up AlertFlow instance.
+
+Please see the repo [Runner](https://github.com/AlertFlow/runner) for more informations.
+
+## Project Structure
+
+The project structure is organized as follows:
+
+- **backend**: Contains the backend code for handling API requests, database interactions, and business logic.
+- **frontend**: Contains the frontend code for the user interface, including components, pages, and styles.
+
+## Local Development
To get started with the AlertFlow project, follow these steps:
@@ -37,7 +92,7 @@ To get started with the AlertFlow project, follow these steps:
cd services/backend && go mod download
```
-3. Create a [config.yaml](https://github.com/AlertFlow/alertflow/blob/main/services/backend/config/config.yaml) file in the `config` directory and add the necessary configuration:
+3. Create a [config.yaml](https://github.com/AlertFlow/alertflow/blob/main/services/backend/config/config.yaml) file and add the necessary configuration:
```yaml
LogLevel: info
@@ -48,14 +103,18 @@ To get started with the AlertFlow project, follow these steps:
User: postgres
Password: postgres
+ Encryption:
+ Enabled: true
+ Key: your-encryption-key
+
JWT:
Secret: your-jwt-secret
```
4. Build and run the backend server:
```sh
- go build -o alertflow-backend
- ./alertflow-backend --config config/config.yaml
+ $ go build -o alertflow-backend
+ $ ./alertflow-backend --config config/config.yaml
```
### Frontend
@@ -80,17 +139,6 @@ To get started with the AlertFlow project, follow these steps:
npm run dev
```
-## Usage
-
-Once the development server is running, you can access the application at `http://localhost:3000`. From there, you can create and manage projects, invite team members, and monitor project activities.
-
-## Project Structure
-
-The project structure is organized as follows:
-
-- **backend**: Contains the backend code for handling API requests, database interactions, and business logic.
-- **frontend**: Contains the frontend code for the user interface, including components, pages, and styles.
-
## Contributing
We welcome contributions to the AlertFlow project! To contribute, follow these steps:
@@ -112,4 +160,4 @@ We welcome contributions to the AlertFlow project! To contribute, follow these s
## License
-This project is licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3. See the [LICENSE](https://github.com/AlertFlow/alertflow/blob/main/LICENSE) file for details.
\ No newline at end of file
+This project is licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3. See the [LICENSE](https://github.com/AlertFlow/alertflow/blob/main/LICENSE) file for details.
diff --git a/services/backend/database/migrations/3_encrypted_flows.go b/services/backend/database/migrations/3_encrypted_flows.go
new file mode 100644
index 00000000..94cd8b79
--- /dev/null
+++ b/services/backend/database/migrations/3_encrypted_flows.go
@@ -0,0 +1,93 @@
+package migrations
+
+import (
+ "context"
+ "fmt"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
+ return addEncryptPayloadsToFlows(ctx, db)
+ }, func(ctx context.Context, db *bun.DB) error {
+ return removeEncryptPayloadsFromFlows(ctx, db)
+ })
+}
+
+func addEncryptPayloadsToFlows(ctx context.Context, db *bun.DB) error {
+ exists, err := columnExists(ctx, db, "flows", "encrypt_payloads")
+ if err != nil {
+ return fmt.Errorf("failed to check if encrypt_payloads column exists: %v", err)
+ }
+ if !exists {
+ _, err := db.NewAddColumn().
+ Table("flows").
+ ColumnExpr("encrypt_payloads BOOL DEFAULT true").
+ Exec(ctx)
+
+ if err != nil {
+ return fmt.Errorf("failed to add encrypt_payloads column to flows table: %v", err)
+ }
+ } else {
+ log.Debug("encrypt_payloads column already exists in flows table")
+ }
+
+ exists, err = columnExists(ctx, db, "flows", "encrypt_executions")
+ if err != nil {
+ return fmt.Errorf("failed to check if encrypt_executions column exists: %v", err)
+ }
+ if !exists {
+ _, err := db.NewAddColumn().
+ Table("flows").
+ ColumnExpr("encrypt_executions BOOL DEFAULT true").
+ Exec(ctx)
+
+ if err != nil {
+ return fmt.Errorf("failed to add encrypt_executions column to flows table: %v", err)
+ }
+ } else {
+ log.Debug("encrypt_executions column already exists in flows table")
+ }
+
+ return nil
+}
+
+func removeEncryptPayloadsFromFlows(ctx context.Context, db *bun.DB) error {
+ exists, err := columnExists(ctx, db, "flows", "encrypt_payloads")
+ if err != nil {
+ return fmt.Errorf("failed to check if encrypt_payloads column exists: %v", err)
+ }
+ if exists {
+ _, err := db.NewDropColumn().
+ Table("flows").
+ Column("encrypt_payloads").
+ Exec(ctx)
+
+ if err != nil {
+ return fmt.Errorf("failed to remove encrypt_payloads column from flows table: %v", err)
+ }
+ } else {
+ log.Debug("encrypt_payloads column already removed from flows table")
+ }
+
+ exists, err = columnExists(ctx, db, "flows", "encrypt_executions")
+ if err != nil {
+ return fmt.Errorf("failed to check if encrypt_executions column exists: %v", err)
+ }
+ if exists {
+ _, err := db.NewDropColumn().
+ Table("flows").
+ Column("encrypt_executions").
+ Exec(ctx)
+
+ if err != nil {
+ return fmt.Errorf("failed to remove encrypt_executions column from flows table: %v", err)
+ }
+ } else {
+ log.Debug("encrypt_executions column already removed from flows table")
+ }
+
+ return nil
+}
diff --git a/services/backend/database/migrations/4_encrypted_flow_components.go b/services/backend/database/migrations/4_encrypted_flow_components.go
new file mode 100644
index 00000000..73c319bf
--- /dev/null
+++ b/services/backend/database/migrations/4_encrypted_flow_components.go
@@ -0,0 +1,93 @@
+package migrations
+
+import (
+ "context"
+ "fmt"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
+ return addEncryptedToFlowComponents(ctx, db)
+ }, func(ctx context.Context, db *bun.DB) error {
+ return removeEncryptedFromFlowComponents(ctx, db)
+ })
+}
+
+func addEncryptedToFlowComponents(ctx context.Context, db *bun.DB) error {
+ exists, err := columnExists(ctx, db, "payloads", "encrypted")
+ if err != nil {
+ return fmt.Errorf("failed to check if encrypted column exists: %v", err)
+ }
+ if !exists {
+ _, err := db.NewAddColumn().
+ Table("payloads").
+ ColumnExpr("encrypted BOOL DEFAULT false").
+ Exec(ctx)
+
+ if err != nil {
+ return fmt.Errorf("failed to add encrypted column to payloads table: %v", err)
+ }
+ } else {
+ log.Debug("encrypted column already exists in payloads table")
+ }
+
+ exists, err = columnExists(ctx, db, "execution_steps", "encrypted")
+ if err != nil {
+ return fmt.Errorf("failed to check if encrypted column exists: %v", err)
+ }
+ if !exists {
+ _, err := db.NewAddColumn().
+ Table("execution_steps").
+ ColumnExpr("encrypted BOOL DEFAULT false").
+ Exec(ctx)
+
+ if err != nil {
+ return fmt.Errorf("failed to add encrypted column to execution_steps table: %v", err)
+ }
+ } else {
+ log.Debug("encrypted column already exists in execution_steps table")
+ }
+
+ return nil
+}
+
+func removeEncryptedFromFlowComponents(ctx context.Context, db *bun.DB) error {
+ exists, err := columnExists(ctx, db, "payloads", "encrypted")
+ if err != nil {
+ return fmt.Errorf("failed to check if encrypted column exists: %v", err)
+ }
+ if exists {
+ _, err := db.NewDropColumn().
+ Table("payloads").
+ Column("encrypted").
+ Exec(ctx)
+
+ if err != nil {
+ return fmt.Errorf("failed to remove encrypted column from payloads table: %v", err)
+ }
+ } else {
+ log.Debug("encrypted column already removed from payloads table")
+ }
+
+ exists, err = columnExists(ctx, db, "execution_steps", "encrypted")
+ if err != nil {
+ return fmt.Errorf("failed to check if encrypted column exists: %v", err)
+ }
+ if exists {
+ _, err := db.NewDropColumn().
+ Table("execution_steps").
+ Column("encrypted").
+ Exec(ctx)
+
+ if err != nil {
+ return fmt.Errorf("failed to remove encrypted column from execution_steps table: %v", err)
+ }
+ } else {
+ log.Debug("encrypted column already removed from execution_steps table")
+ }
+
+ return nil
+}
diff --git a/services/backend/functions/encryption/execution_step_action_message.go b/services/backend/functions/encryption/execution_step_action_message.go
new file mode 100644
index 00000000..00fc1de0
--- /dev/null
+++ b/services/backend/functions/encryption/execution_step_action_message.go
@@ -0,0 +1,61 @@
+package encryption
+
+import (
+ "alertflow-backend/config"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "encoding/base64"
+ "io"
+)
+
+func EncryptExecutionStepActionMessage(messages []string) ([]string, error) {
+ block, err := aes.NewCipher([]byte(config.Config.Encryption.Key))
+ if err != nil {
+ return nil, err
+ }
+
+ for i := range messages {
+ plaintext := []byte(messages[i])
+ ciphertext := make([]byte, aes.BlockSize+len(plaintext))
+ iv := ciphertext[:aes.BlockSize]
+
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ return nil, err
+ }
+
+ stream := cipher.NewCFBEncrypter(block, iv)
+ stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
+
+ // Encode the ciphertext as base64 to ensure it can be stored as JSON
+ encodedCiphertext := base64.StdEncoding.EncodeToString(ciphertext)
+ messages[i] = encodedCiphertext
+ }
+
+ return messages, nil
+}
+
+func DecryptExecutionStepActionMessage(encryptedMessage []string) ([]string, error) {
+ block, err := aes.NewCipher([]byte(config.Config.Encryption.Key))
+ if err != nil {
+ return nil, err
+ }
+
+ for i := range encryptedMessage {
+ encodedCiphertext := encryptedMessage[i]
+ ciphertext, err := base64.StdEncoding.DecodeString(encodedCiphertext)
+ if err != nil {
+ return nil, err
+ }
+
+ iv := ciphertext[:aes.BlockSize]
+ ciphertext = ciphertext[aes.BlockSize:]
+
+ stream := cipher.NewCFBDecrypter(block, iv)
+ stream.XORKeyStream(ciphertext, ciphertext)
+
+ encryptedMessage[i] = string(ciphertext)
+ }
+
+ return encryptedMessage, nil
+}
diff --git a/services/backend/functions/encryption/payload.go b/services/backend/functions/encryption/payload.go
new file mode 100644
index 00000000..7d43b380
--- /dev/null
+++ b/services/backend/functions/encryption/payload.go
@@ -0,0 +1,63 @@
+package encryption
+
+import (
+ "alertflow-backend/config"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "encoding/base64"
+ "encoding/json"
+ "io"
+)
+
+func EncryptPayload(payload json.RawMessage) (json.RawMessage, error) {
+ block, err := aes.NewCipher([]byte(config.Config.Encryption.Key))
+ if err != nil {
+ return nil, err
+ }
+
+ plaintext := []byte(payload)
+ ciphertext := make([]byte, aes.BlockSize+len(plaintext))
+ iv := ciphertext[:aes.BlockSize]
+
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ return nil, err
+ }
+
+ stream := cipher.NewCFBEncrypter(block, iv)
+ stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
+
+ // Encode the ciphertext as base64 to ensure it can be stored as JSON
+ encodedCiphertext := base64.StdEncoding.EncodeToString(ciphertext)
+ encryptedPayload, err := json.Marshal(encodedCiphertext)
+ if err != nil {
+ return nil, err
+ }
+
+ return json.RawMessage(encryptedPayload), nil
+}
+
+func DecryptPayload(payload json.RawMessage) (json.RawMessage, error) {
+ block, err := aes.NewCipher([]byte(config.Config.Encryption.Key))
+ if err != nil {
+ return nil, err
+ }
+
+ var encodedCiphertext string
+ if err := json.Unmarshal(payload, &encodedCiphertext); err != nil {
+ return nil, err
+ }
+
+ ciphertext, err := base64.StdEncoding.DecodeString(encodedCiphertext)
+ if err != nil {
+ return nil, err
+ }
+
+ iv := ciphertext[:aes.BlockSize]
+ ciphertext = ciphertext[aes.BlockSize:]
+
+ stream := cipher.NewCFBDecrypter(block, iv)
+ stream.XORKeyStream(ciphertext, ciphertext)
+
+ return json.RawMessage(ciphertext), nil
+}
diff --git a/services/backend/handlers/executions/create_step.go b/services/backend/handlers/executions/create_step.go
index 8306a187..3377e970 100644
--- a/services/backend/handlers/executions/create_step.go
+++ b/services/backend/handlers/executions/create_step.go
@@ -1,6 +1,7 @@
package executions
import (
+ "alertflow-backend/functions/encryption"
"alertflow-backend/functions/httperror"
"alertflow-backend/models"
"net/http"
@@ -17,8 +18,34 @@ func CreateStep(context *gin.Context, db *bun.DB) {
return
}
+ // get parent execution data
+ var execution models.Executions
+ err := db.NewSelect().Model(&execution).Column("flow_id").Where("id = ?", step.ExecutionID).Scan(context)
+ if err != nil {
+ httperror.InternalServerError(context, "Error fetching parent execution data", err)
+ return
+ }
+ // get flow data
+ var flow models.Flows
+ err = db.NewSelect().Model(&flow).Where("id = ?", execution.FlowID).Scan(context)
+ if err != nil {
+ httperror.InternalServerError(context, "Error fetching flow data", err)
+ return
+ }
+
+ // check for ecryption
+ if flow.EncryptExecutions && step.ActionMessages != nil && len(step.ActionMessages) > 0 {
+ step.ActionMessages, err = encryption.EncryptExecutionStepActionMessage(step.ActionMessages)
+ if err != nil {
+ httperror.InternalServerError(context, "Error encrypting execution step action messages", err)
+ return
+ }
+
+ step.Encrypted = true
+ }
+
step.ID = uuid.New()
- _, err := db.NewInsert().Model(&step).Exec(context)
+ _, err = db.NewInsert().Model(&step).Exec(context)
if err != nil {
httperror.InternalServerError(context, "Error creating execution step on db", err)
return
diff --git a/services/backend/handlers/executions/get_step.go b/services/backend/handlers/executions/get_step.go
index f79929b3..319d3e1e 100644
--- a/services/backend/handlers/executions/get_step.go
+++ b/services/backend/handlers/executions/get_step.go
@@ -1,6 +1,7 @@
package executions
import (
+ "alertflow-backend/functions/encryption"
"alertflow-backend/functions/httperror"
"alertflow-backend/models"
"net/http"
@@ -20,5 +21,13 @@ func GetStep(context *gin.Context, db *bun.DB) {
return
}
+ if step.Encrypted {
+ step.ActionMessages, err = encryption.DecryptExecutionStepActionMessage(step.ActionMessages)
+ if err != nil {
+ httperror.InternalServerError(context, "Error decrypting execution step action messages", err)
+ return
+ }
+ }
+
context.JSON(http.StatusOK, gin.H{"step": step})
}
diff --git a/services/backend/handlers/executions/get_steps.go b/services/backend/handlers/executions/get_steps.go
index a23297cb..3ad6a934 100644
--- a/services/backend/handlers/executions/get_steps.go
+++ b/services/backend/handlers/executions/get_steps.go
@@ -1,6 +1,7 @@
package executions
import (
+ "alertflow-backend/functions/encryption"
"alertflow-backend/functions/httperror"
"alertflow-backend/models"
"net/http"
@@ -19,5 +20,15 @@ func GetSteps(context *gin.Context, db *bun.DB) {
return
}
+ for i := range steps {
+ if steps[i].Encrypted {
+ steps[i].ActionMessages, err = encryption.DecryptExecutionStepActionMessage(steps[i].ActionMessages)
+ if err != nil {
+ httperror.InternalServerError(context, "Error decrypting execution step action messages", err)
+ return
+ }
+ }
+ }
+
context.JSON(http.StatusOK, gin.H{"steps": steps})
}
diff --git a/services/backend/handlers/executions/update_step.go b/services/backend/handlers/executions/update_step.go
index b386c4a7..75cc6248 100644
--- a/services/backend/handlers/executions/update_step.go
+++ b/services/backend/handlers/executions/update_step.go
@@ -1,6 +1,7 @@
package executions
import (
+ "alertflow-backend/functions/encryption"
"alertflow-backend/functions/httperror"
"alertflow-backend/models"
"net/http"
@@ -26,6 +27,30 @@ func UpdateStep(context *gin.Context, db *bun.DB) {
return
}
+ // get parent execution data
+ var execution models.Executions
+ err = db.NewSelect().Model(&execution).Column("flow_id").Where("id = ?", dbStep.ExecutionID).Scan(context)
+ if err != nil {
+ httperror.InternalServerError(context, "Error fetching parent execution data", err)
+ return
+ }
+ // get flow data
+ var flow models.Flows
+ err = db.NewSelect().Model(&flow).Where("id = ?", execution.FlowID).Scan(context)
+ if err != nil {
+ httperror.InternalServerError(context, "Error fetching flow data", err)
+ return
+ }
+
+ // check for ecryption and decrypt if needed
+ if flow.EncryptExecutions && dbStep.ActionMessages != nil && len(dbStep.ActionMessages) > 0 {
+ dbStep.ActionMessages, err = encryption.DecryptExecutionStepActionMessage(dbStep.ActionMessages)
+ if err != nil {
+ httperror.InternalServerError(context, "Error decrypting execution step action messages", err)
+ return
+ }
+ }
+
// append new message to existing
step.ActionMessages = append(dbStep.ActionMessages, step.ActionMessages...)
@@ -41,6 +66,17 @@ func UpdateStep(context *gin.Context, db *bun.DB) {
step.RunnerID = dbStep.RunnerID
}
+ // check for ecryption and encrypt if needed
+ if flow.EncryptExecutions && step.ActionMessages != nil && len(step.ActionMessages) > 0 {
+ step.ActionMessages, err = encryption.EncryptExecutionStepActionMessage(step.ActionMessages)
+ if err != nil {
+ httperror.InternalServerError(context, "Error encrypting execution step action messages", err)
+ return
+ }
+
+ step.Encrypted = true
+ }
+
_, err = db.NewUpdate().Model(&step).Column(
"action_messages",
"runner_id",
@@ -62,6 +98,7 @@ func UpdateStep(context *gin.Context, db *bun.DB) {
"finished",
"started_at",
"finished_at",
+ "encrypted",
).Where("id = ?", stepID).Exec(context)
if err != nil {
httperror.InternalServerError(context, "Error updating step on db", err)
diff --git a/services/backend/handlers/flows/create_payload.go b/services/backend/handlers/flows/create_payload.go
index c1198917..46d00ba0 100644
--- a/services/backend/handlers/flows/create_payload.go
+++ b/services/backend/handlers/flows/create_payload.go
@@ -1,6 +1,8 @@
package flows
import (
+ "alertflow-backend/config"
+ "alertflow-backend/functions/encryption"
functions "alertflow-backend/functions/flow"
"alertflow-backend/functions/httperror"
"alertflow-backend/models"
@@ -22,7 +24,7 @@ func CreatePayload(context *gin.Context, db *bun.DB) {
}
var flow models.Flows
- flowCount, err := db.NewSelect().Model(&flow).Where("id = ?", payload.FlowID).Count(context)
+ flowCount, err := db.NewSelect().Model(&flow).Where("id = ?", payload.FlowID).ScanAndCount(context)
if err != nil {
httperror.InternalServerError(context, "Error collecting flow data from db", err)
return
@@ -33,7 +35,18 @@ func CreatePayload(context *gin.Context, db *bun.DB) {
}
payload.ID = uuid.New()
- res, err := db.NewInsert().Model(&payload).Column("id", "payload", "flow_id", "runner_id", "endpoint").Exec(context)
+
+ // encrypt payload if enabled
+ if flow.EncryptPayloads && config.Config.Encryption.Enabled {
+ payload.Payload, err = encryption.EncryptPayload(payload.Payload)
+ if err != nil {
+ httperror.InternalServerError(context, "Error encrypting payload", err)
+ return
+ }
+ payload.Encrypted = true
+ }
+
+ res, err := db.NewInsert().Model(&payload).Column("id", "payload", "flow_id", "runner_id", "endpoint", "encrypted").Exec(context)
if err != nil {
httperror.InternalServerError(context, "Error creating payload on db", err)
return
diff --git a/services/backend/handlers/flows/get_payloads.go b/services/backend/handlers/flows/get_payloads.go
index 3b1250e6..ba47d7df 100644
--- a/services/backend/handlers/flows/get_payloads.go
+++ b/services/backend/handlers/flows/get_payloads.go
@@ -1,6 +1,7 @@
package flows
import (
+ "alertflow-backend/functions/encryption"
"alertflow-backend/functions/gatekeeper"
"alertflow-backend/functions/httperror"
"alertflow-backend/models"
@@ -41,5 +42,15 @@ func GetFlowPayloads(context *gin.Context, db *bun.DB) {
return
}
+ for i := range payloads {
+ if payloads[i].Encrypted {
+ payloads[i].Payload, err = encryption.DecryptPayload(payloads[i].Payload)
+ if err != nil {
+ httperror.InternalServerError(context, "Error decrypting payload", err)
+ return
+ }
+ }
+ }
+
context.JSON(http.StatusOK, gin.H{"payloads": payloads})
}
diff --git a/services/backend/handlers/flows/update.go b/services/backend/handlers/flows/update.go
index cb98ba6f..cd316c26 100644
--- a/services/backend/handlers/flows/update.go
+++ b/services/backend/handlers/flows/update.go
@@ -47,7 +47,7 @@ func UpdateFlow(context *gin.Context, db *bun.DB) {
}
flow.UpdatedAt = time.Now()
- _, err = db.NewUpdate().Model(&flow).Column("name", "description", "project_id", "runner_id", "updated_at").Where("id = ?", flowID).Exec(context)
+ _, err = db.NewUpdate().Model(&flow).Column("name", "description", "project_id", "runner_id", "encrypt_action_params", "encrypt_payloads", "encrypt_executions", "updated_at").Where("id = ?", flowID).Exec(context)
if err != nil {
httperror.InternalServerError(context, "Error updating flow on db", err)
return
diff --git a/services/backend/handlers/payloads/get_payload.go b/services/backend/handlers/payloads/get_payload.go
index 1dc9aeb4..53884267 100644
--- a/services/backend/handlers/payloads/get_payload.go
+++ b/services/backend/handlers/payloads/get_payload.go
@@ -1,6 +1,7 @@
package payloads
import (
+ "alertflow-backend/functions/encryption"
"alertflow-backend/functions/gatekeeper"
"alertflow-backend/functions/httperror"
"alertflow-backend/models"
@@ -40,5 +41,13 @@ func GetSingle(context *gin.Context, db *bun.DB) {
return
}
+ if payload.Encrypted {
+ payload.Payload, err = encryption.DecryptPayload(payload.Payload)
+ if err != nil {
+ httperror.InternalServerError(context, "Error decrypting payload", err)
+ return
+ }
+ }
+
context.JSON(http.StatusOK, gin.H{"payload": payload})
}
diff --git a/services/backend/handlers/payloads/get_payloads.go b/services/backend/handlers/payloads/get_payloads.go
index 00164b8f..1c808acb 100644
--- a/services/backend/handlers/payloads/get_payloads.go
+++ b/services/backend/handlers/payloads/get_payloads.go
@@ -1,6 +1,7 @@
package payloads
import (
+ "alertflow-backend/functions/encryption"
"alertflow-backend/functions/httperror"
"alertflow-backend/models"
"net/http"
@@ -18,5 +19,15 @@ func GetMultiple(context *gin.Context, db *bun.DB) {
return
}
+ for i := range payloads {
+ if payloads[i].Encrypted {
+ payloads[i].Payload, err = encryption.DecryptPayload(payloads[i].Payload)
+ if err != nil {
+ httperror.InternalServerError(context, "Error decrypting payload", err)
+ return
+ }
+ }
+ }
+
context.JSON(http.StatusOK, gin.H{"payloads": payloads})
}
diff --git a/services/backend/main.go b/services/backend/main.go
index 53c0871e..fb7f76a7 100644
--- a/services/backend/main.go
+++ b/services/backend/main.go
@@ -11,7 +11,7 @@ import (
log "github.com/sirupsen/logrus"
)
-const version string = "1.0.0-beta13"
+const version string = "1.0.0-beta14"
var (
configFile = kingpin.Flag("config", "Config file").Short('c').Default("config.yaml").String()
diff --git a/services/backend/models/execution_steps.go b/services/backend/models/execution_steps.go
index 03ce6dc9..2497366c 100644
--- a/services/backend/models/execution_steps.go
+++ b/services/backend/models/execution_steps.go
@@ -39,4 +39,5 @@ type ExecutionSteps struct {
CreatedAt time.Time `bun:"created_at,type:timestamptz,default:now()" json:"created_at"`
StartedAt time.Time `bun:"started_at,type:timestamptz" json:"started_at"`
FinishedAt time.Time `bun:"finished_at,type:timestamptz" json:"finished_at"`
+ Encrypted bool `bun:"encrypted,type:bool,default:false" json:"encrypted"`
}
diff --git a/services/backend/models/flows.go b/services/backend/models/flows.go
index 369779a8..0c2009e5 100644
--- a/services/backend/models/flows.go
+++ b/services/backend/models/flows.go
@@ -25,6 +25,8 @@ type Flows struct {
CreatedAt time.Time `bun:"created_at,type:timestamptz,default:now()" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at,type:timestamptz" json:"updated_at"`
EncryptActionParams bool `bun:"encrypt_action_params,type:bool,default:true" json:"encrypt_action_params"`
+ EncryptPayloads bool `bun:"encrypt_payloads,type:bool,default:true" json:"encrypt_payloads"`
+ EncryptExecutions bool `bun:"encrypt_executions,type:bool,default:true" json:"encrypt_executions"`
}
type Actions struct {
diff --git a/services/backend/models/payloads.go b/services/backend/models/payloads.go
index 374d8000..1caf69e9 100644
--- a/services/backend/models/payloads.go
+++ b/services/backend/models/payloads.go
@@ -18,4 +18,5 @@ type Payloads struct {
RunnerID string `bun:"runner_id,type:text,default:''" json:"runner_id"`
Endpoint string `bun:"endpoint,type:text,default:''" json:"endpoint"`
CreatedAt time.Time `bun:"created_at,type:timestamptz,default:now()" json:"created_at"`
+ Encrypted bool `bun:"encrypted,type:bool,default:false" json:"encrypted"`
}
diff --git a/services/frontend/components/dashboard/flows/flow/encryption.tsx b/services/frontend/components/dashboard/flows/flow/encryption.tsx
new file mode 100644
index 00000000..251bd2fc
--- /dev/null
+++ b/services/frontend/components/dashboard/flows/flow/encryption.tsx
@@ -0,0 +1,133 @@
+import { Card, CardBody, Spacer, Switch } from "@nextui-org/react";
+import { useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
+import { toast } from "sonner";
+
+import UpdateFlow from "@/lib/fetch/flow/PUT/UpdateFlow";
+import ErrorCard from "@/components/error/ErrorCard";
+
+export default function FlowEncryption({ flow }: { flow: any }) {
+ const router = useRouter();
+
+ const [encryptActionParams, setEncryptActionParams] = useState(
+ flow.encrypt_action_params,
+ );
+ const [encryptPayloads, setEncryptPayloads] = useState(flow.encrypt_payloads);
+ const [encryptExecutions, setEncryptExecutions] = useState(
+ flow.encrypt_executions,
+ );
+
+ const [error, setError] = useState(false);
+ const [errorMessage, setErrorMessage] = useState("");
+
+ useEffect(() => {
+ setEncryptActionParams(flow.encrypt_action_params);
+ setEncryptPayloads(flow.encrypt_payloads);
+ setEncryptExecutions(flow.encrypt_executions);
+ }, [flow]);
+
+ useEffect(() => {
+ if (
+ encryptActionParams === flow.encrypt_action_params &&
+ encryptPayloads === flow.encrypt_payloads &&
+ encryptExecutions === flow.encrypt_executions
+ ) {
+ return;
+ }
+ updateFlow();
+ }, [encryptActionParams, encryptPayloads, encryptExecutions]);
+
+ async function updateFlow() {
+ const response = (await UpdateFlow(
+ flow.id,
+ flow.name,
+ flow.description,
+ flow.project_id,
+ flow.runner_id,
+ encryptActionParams,
+ encryptPayloads,
+ encryptExecutions,
+ )) as any;
+
+ if (!response) {
+ setError(true);
+ setErrorMessage("An error occurred while updating the flow");
+
+ return;
+ }
+
+ if (response.success) {
+ router.refresh();
+ toast.success("Flow updated successfully");
+ } else {
+ setError(true);
+ setErrorMessage(response.message);
+ toast.error("Failed to update flow");
+ }
+ }
+
+ return (
+ <>
+ {error &&
Encrypted Action Parameters
++ All action parameters will be encrypted. +
+Encrypted Payloads
++ Incoming payloads will be encrypted stored on the db when they + reached the backend +
+Encypted Executions
++ All execution action messages will be stored encrypted on the db +
+Step not started yet
) : (
-
+