Skip to content
24 changes: 22 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=difyai123456
REDIS_DB=0
REDIS_USE_SSL=false
# SSL configuration for Redis (when REDIS_USE_SSL=true)
REDIS_SSL_CERT_REQS=CERT_NONE
# REDIS_SSL_CERT_REQS controls how server certificates are verified:
# - CERT_NONE: Skips all certificate verification (insecure, sets InsecureSkipVerify=true)
# Use only in development/testing environments. This is the default in this example file.
# - CERT_OPTIONAL: Requires valid certificate verification (same as CERT_REQUIRED for client-side TLS)
# CERT_OPTIONAL is treated as CERT_REQUIRED because servers almost always present
# certificates, and the client's choice is whether to validate them or not
# Uses system's default CA certificates if REDIS_SSL_CA_CERTS is not provided
# - CERT_REQUIRED: Requires valid certificate verification (most secure, sets InsecureSkipVerify=false)
# Recommended for production environments
# IMPORTANT: REDIS_SSL_CA_CERTS must be provided, otherwise the application will fail to start
# - Empty string: Behaves like CERT_OPTIONAL (secure, enables verification, but allows system CA certificates)
# This is the default when REDIS_SSL_CERT_REQS is not set
REDIS_SSL_CA_CERTS=
# Path to the CA certificate file for SSL verification, e.g. /path/to/ca.crt
# REQUIRED when REDIS_SSL_CERT_REQS=CERT_REQUIRED
# Optional for CERT_OPTIONAL (uses system's default CA certificates if not provided)
# Ignored when REDIS_SSL_CERT_REQS=CERT_NONE

# Whether to use Redis Sentinel mode.
# If set to true, the application will automatically discover and connect to the master node through Sentinel.
Expand All @@ -98,8 +118,8 @@ DB_PASSWORD=difyai123456
DB_HOST=localhost
DB_PORT=5432
DB_DATABASE=dify_plugin
# Specifies the SSL mode for the database connection.
# Possible values include 'disable', 'require', 'verify-ca', and 'verify-full'.
# Specifies the SSL mode for the database connection.
# Possible values include 'disable', 'require', 'verify-ca', and 'verify-full'.
# 'disable' means SSL is not used for the connection.
DB_SSL_MODE=disable
# database connection pool settings
Expand Down
2 changes: 1 addition & 1 deletion internal/cluster/clutser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func createSimulationCluster(nums int) ([]*Cluster, error) {
err := cache.InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0)
err := cache.InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0, nil)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/core/debugging_runtime/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (n *TestPluginRuntimeNotifier) OnServerShutdown(reason ServerShutdownReason

// TestAcceptConnection tests the acceptance of the connection
func TestAcceptConnection(t *testing.T) {
if cache.InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0) != nil {
if cache.InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0, nil) != nil {
t.Errorf("failed to init redis client")
return
}
Expand Down Expand Up @@ -338,7 +338,7 @@ func TestNoHandleShakeIn10Seconds(t *testing.T) {
}

func TestIncorrectHandshake(t *testing.T) {
if cache.InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0) != nil {
if cache.InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0, nil) != nil {
t.Errorf("failed to init redis client")
return
}
Expand Down
8 changes: 4 additions & 4 deletions internal/core/persistence/persistence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

func TestPersistenceStoreAndLoad(t *testing.T) {
err := cache.InitRedisClient("localhost:6379", "", "difyai123456", false, 0)
err := cache.InitRedisClient("localhost:6379", "", "difyai123456", false, 0, nil)
if err != nil {
t.Fatalf("Failed to init redis client: %v", err)
}
Expand Down Expand Up @@ -70,7 +70,7 @@ func TestPersistenceStoreAndLoad(t *testing.T) {
}

func TestPersistenceSaveAndLoadWithLongKey(t *testing.T) {
err := cache.InitRedisClient("localhost:6379", "", "difyai123456", false, 0)
err := cache.InitRedisClient("localhost:6379", "", "difyai123456", false, 0, nil)
assert.Nil(t, err)
defer cache.Close()
db.Init(&app.Config{
Expand Down Expand Up @@ -104,7 +104,7 @@ func TestPersistenceSaveAndLoadWithLongKey(t *testing.T) {
}

func TestPersistenceDelete(t *testing.T) {
err := cache.InitRedisClient("localhost:6379", "", "difyai123456", false, 0)
err := cache.InitRedisClient("localhost:6379", "", "difyai123456", false, 0, nil)
assert.Nil(t, err)
defer cache.Close()
db.Init(&app.Config{
Expand Down Expand Up @@ -151,7 +151,7 @@ func TestPersistenceDelete(t *testing.T) {
}

func TestPersistencePathTraversal(t *testing.T) {
err := cache.InitRedisClient("localhost:6379", "", "difyai123456", false, 0)
err := cache.InitRedisClient("localhost:6379", "", "difyai123456", false, 0, nil)
if err != nil {
t.Fatalf("Failed to init redis client: %v", err)
}
Expand Down
8 changes: 8 additions & 0 deletions internal/core/plugin_manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ func (p *PluginManager) GetAsset(id string) ([]byte, error) {
func (p *PluginManager) Launch(configuration *app.Config) {
log.Info("start plugin manager daemon")

// Build TLS config for Redis (nil when RedisUseSsl=false)
tlsConf, err := configuration.RedisTLSConfig()
if err != nil {
log.Panic("invalid Redis TLS config: %s", err.Error())
}

// init redis client
if configuration.RedisUseSentinel {
// use Redis Sentinel
Expand All @@ -116,6 +122,7 @@ func (p *PluginManager) Launch(configuration *app.Config) {
configuration.RedisUseSsl,
configuration.RedisDB,
configuration.RedisSentinelSocketTimeout,
tlsConf, // pass TLS to cache initializer
); err != nil {
log.Panic("init redis sentinel client failed", "error", err)
}
Expand All @@ -126,6 +133,7 @@ func (p *PluginManager) Launch(configuration *app.Config) {
configuration.RedisPass,
configuration.RedisUseSsl,
configuration.RedisDB,
tlsConf, // pass TLS to cache initializer
); err != nil {
log.Panic("init redis client failed", "error", err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/core/session_manager/session_trace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestGetSessionTraceInMemory(t *testing.T) {
}

func TestGetSessionTraceFromDistributedCache(t *testing.T) {
require.NoError(t, cache.InitRedisClient("127.0.0.1:6379", "", "difyai123456", false, 0))
require.NoError(t, cache.InitRedisClient("127.0.0.1:6379", "", "difyai123456", false, 0, nil))
t.Cleanup(func() {
cache.Close()
})
Expand Down
2 changes: 1 addition & 1 deletion internal/service/debugging_service/connection_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

func TestConnectionKey(t *testing.T) {
err := cache.InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0)
err := cache.InitRedisClient("0.0.0.0:6379", "", "difyai123456", false, 0, nil)
if err != nil {
t.Errorf("init redis client failed: %v", err)
return
Expand Down
68 changes: 61 additions & 7 deletions internal/types/app/config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package app

import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"strings"

"github.com/go-playground/validator/v10"
"github.com/langgenius/dify-plugin-daemon/pkg/entities/plugin_entities"
Expand Down Expand Up @@ -68,7 +72,7 @@ type Config struct {
HuaweiOBSAccessKey string `envconfig:"HUAWEI_OBS_ACCESS_KEY"`
HuaweiOBSSecretKey string `envconfig:"HUAWEI_OBS_SECRET_KEY"`
HuaweiOBSServer string `envconfig:"HUAWEI_OBS_SERVER"`
HuaweiOBSPathStyle bool `envconfig:"HUAWEI_OBS_PATH_STYLE" default:"false"`
HuaweiOBSPathStyle bool `envconfig:"HUAWEI_OBS_PATH_STYLE" default:"false"`

// volcengine tos
VolcengineTOSEndpoint string `envconfig:"VOLCENGINE_TOS_ENDPOINT"`
Expand Down Expand Up @@ -109,12 +113,14 @@ type Config struct {
RoutinePoolSize int `envconfig:"ROUTINE_POOL_SIZE" validate:"required"`

// redis
RedisHost string `envconfig:"REDIS_HOST"`
RedisPort uint16 `envconfig:"REDIS_PORT"`
RedisPass string `envconfig:"REDIS_PASSWORD"`
RedisUser string `envconfig:"REDIS_USERNAME"`
RedisUseSsl bool `envconfig:"REDIS_USE_SSL"`
RedisDB int `envconfig:"REDIS_DB"`
RedisHost string `envconfig:"REDIS_HOST"`
RedisPort uint16 `envconfig:"REDIS_PORT"`
RedisPass string `envconfig:"REDIS_PASSWORD"`
RedisUser string `envconfig:"REDIS_USERNAME"`
RedisDB int `envconfig:"REDIS_DB"`
RedisUseSsl bool `envconfig:"REDIS_USE_SSL"`
RedisSSLCertReqs string `envconfig:"REDIS_SSL_CERT_REQS"`
RedisSSLCACerts string `envconfig:"REDIS_SSL_CA_CERTS"`

// redis sentinel
RedisUseSentinel bool `envconfig:"REDIS_USE_SENTINEL"`
Expand Down Expand Up @@ -281,6 +287,54 @@ func (c *Config) GetLocalRuntimeMaxBufferSize() int {
return c.PluginRuntimeMaxBufferSize
}

// RedisTLSConfig builds a *tls.Config for Redis based on envs.
func (c *Config) RedisTLSConfig() (*tls.Config, error) {
if !c.RedisUseSsl {
return nil, nil
}

tlsConf := &tls.Config{
MinVersion: tls.VersionTLS12,
}

// Load custom CA certificates if provided
if strings.TrimSpace(c.RedisSSLCACerts) != "" {
pem, err := os.ReadFile(c.RedisSSLCACerts)
if err != nil {
return nil, fmt.Errorf("read REDIS_SSL_CA_CERTS: %w", err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(pem) {
return nil, fmt.Errorf("failed to append CA certs from %s", c.RedisSSLCACerts)
}
tlsConf.RootCAs = pool
}

// Configure certificate verification based on REDIS_SSL_CERT_REQS
certReqs := strings.ToUpper(strings.TrimSpace(c.RedisSSLCertReqs))
switch certReqs {
case "CERT_NONE":
// Skip all certificate verification (insecure)
tlsConf.InsecureSkipVerify = true
case "CERT_OPTIONAL", "CERT_REQUIRED", "":
// Require valid certificate verification (default and most secure)
// CERT_OPTIONAL is treated as CERT_REQUIRED for client-side TLS,
// as servers almost always present certificates and the client's
// choice is whether to validate them or not
tlsConf.InsecureSkipVerify = false

// Require CA certs to be explicitly provided when CERT_REQUIRED is set
if certReqs == "CERT_REQUIRED" && strings.TrimSpace(c.RedisSSLCACerts) == "" {
return nil, fmt.Errorf("REDIS_SSL_CA_CERTS must be provided when REDIS_SSL_CERT_REQS is set to CERT_REQUIRED")
}
default:
// Invalid value - return an error instead of silently defaulting
return nil, fmt.Errorf("invalid REDIS_SSL_CERT_REQS value: %s (valid options: CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED)", certReqs)
}

return tlsConf, nil
}

type PlatformType string

const (
Expand Down
Loading
Loading