Skip to content
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ bin/*
openspec/
.github/
.claude/
.opencode/
AGENTS.md
CLAUDE.md
48 changes: 42 additions & 6 deletions config/sample_webconfig.conf
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,29 @@ webconfig {
"127.0.0.1"
]
keyspace = "webconfig"
timeout_in_sec = 5
connect_timeout_in_sec = 5
timeout_in_sec = 10
connect_timeout_in_sec = 10
concurrent_queries = 5
connections = 5
connections = 2
local_dc = ""
page_size = 50
user = "dbuser"
test_keyspace = "test_webconfig"
is_ssl_enabled = true

// TCP keepalive interval in seconds (default: 30)
socket_keepalive_sec = 30

// Exponential reconnection policy settings.
// reconnect_max_retries: number of attempts before giving up (0 = never retry).
reconnect_initial_interval_ms = 2000
reconnect_max_retries = 10
reconnect_max_interval_sec = 60

// Disable initial host lookup (default: false).
// Set to true when Cassandra hosts are only accessible by IP and DNS is unavailable.
disable_initial_host_lookup = false

// TLS/SSL configuration (optional when is_ssl_enabled = true)
// If tls block is not provided or incomplete, will use insecure TLS
tls {
Expand All @@ -196,6 +209,11 @@ webconfig {
// Skip certificate verification (INSECURE - for testing only)
// When true, allows TLS without certificates or CA validation
insecure_skip_verify = false

// Override SNI hostname sent during TLS handshake.
// Required when the certificate uses DNS SANs only (no IP SANs).
// Leave empty to use the host IP address (gocql default).
server_name = ""
Comment thread
lstruman marked this conversation as resolved.
}
}

Expand All @@ -205,15 +223,28 @@ webconfig {
"192.168.1.114"
]
keyspace = "yugabytedb"
timeout_in_sec = 5
connect_timeout_in_sec = 5
timeout_in_sec = 10
connect_timeout_in_sec = 10
concurrent_queries = 5
connections = 5
connections = 2
local_dc = ""
page_size = 50
user = "dbuser"
test_keyspace = "test_yugabytedb"

// TCP keepalive interval in seconds (default: 30)
socket_keepalive_sec = 30

// Exponential reconnection policy settings.
// reconnect_max_retries: number of attempts before giving up (0 = never retry).
reconnect_initial_interval_ms = 2000
reconnect_max_retries = 10
reconnect_max_interval_sec = 60

// Disable initial host lookup (default: false).
// Set to true when Cassandra hosts are only accessible by IP and DNS is unavailable.
disable_initial_host_lookup = false

// TODO change to false for CI/CD
is_ssl_enabled = true

Expand All @@ -230,6 +261,11 @@ webconfig {
// Skip certificate verification (INSECURE - for testing only)
// When true, allows TLS without certificates or CA validation
insecure_skip_verify = false

// Override SNI hostname sent during TLS handshake.
// Required when the certificate uses DNS SANs only (no IP SANs).
// Leave empty to use the host IP address (gocql default).
server_name = ""
}
}
}
Expand Down
52 changes: 35 additions & 17 deletions db/cassandra/cassandra_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@ import (
)

const (
ProtocolVersion = 4
DefaultKeyspace = "webconfig"
DefaultTestKeyspace = "test_webconfig"
DisableInitialHostLookup = false
DefaultSleepTimeInMillisecond = 10
DefaultConnections = 2
DefaultPageSize = 50
ProtocolVersion = 4
DefaultKeyspace = "webconfig"
DefaultTestKeyspace = "test_webconfig"
DefaultDisableInitialHostLookup = false
DefaultSleepTimeInMillisecond = 10
DefaultConnections = 2
DefaultPageSize = 50
DefaultTimeoutSec = 10
DefaultConnectTimeoutSec = 10
DefaultSocketKeepaliveSec = 30
DefaultReconnectInitialIntervalMs = 2000
DefaultReconnectMaxRetries = 10
DefaultReconnectMaxIntervalSec = 60
)

// if 'wifi_schema_v2_enabled'=true, v1.3 is also supported
Expand Down Expand Up @@ -103,10 +109,11 @@ func NewCassandraClient(conf *configuration.Config, testOnly bool) (*CassandraCl

cluster.Consistency = gocql.LocalQuorum
cluster.ProtoVersion = ProtocolVersion
cluster.DisableInitialHostLookup = DisableInitialHostLookup
cluster.Timeout = time.Duration(dbconf.GetInt32("timeout_in_sec", 1)) * time.Second
cluster.ConnectTimeout = time.Duration(dbconf.GetInt32("connect_timeout_in_sec", 1)) * time.Second
cluster.DisableInitialHostLookup = dbconf.GetBoolean("disable_initial_host_lookup", DefaultDisableInitialHostLookup)
cluster.Timeout = time.Duration(dbconf.GetInt32("timeout_in_sec", DefaultTimeoutSec)) * time.Second
cluster.ConnectTimeout = time.Duration(dbconf.GetInt32("connect_timeout_in_sec", DefaultConnectTimeoutSec)) * time.Second
cluster.NumConns = int(dbconf.GetInt32("connections", DefaultConnections))
cluster.SocketKeepalive = time.Duration(dbconf.GetInt32("socket_keepalive_sec", DefaultSocketKeepaliveSec)) * time.Second

cluster.RetryPolicy = &gocql.DowngradingConsistencyRetryPolicy{
ConsistencyLevelsToTry: []gocql.Consistency{
Expand All @@ -116,9 +123,20 @@ func NewCassandraClient(conf *configuration.Config, testOnly bool) (*CassandraCl
},
}

reconnectIntervalMs := dbconf.GetInt32("reconnect_initial_interval_ms", DefaultReconnectInitialIntervalMs)
reconnectMaxRetries := int(dbconf.GetInt32("reconnect_max_retries", DefaultReconnectMaxRetries))
reconnectMaxIntervalSec := dbconf.GetInt32("reconnect_max_interval_sec", DefaultReconnectMaxIntervalSec)
cluster.ReconnectionPolicy = &gocql.ExponentialReconnectionPolicy{
InitialInterval: time.Duration(reconnectIntervalMs) * time.Millisecond,
MaxRetries: reconnectMaxRetries,
MaxInterval: time.Duration(reconnectMaxIntervalSec) * time.Second,
}
Comment thread
lstruman marked this conversation as resolved.

localDc := dbconf.GetString("local_dc")
if len(localDc) > 0 {
cluster.PoolConfig.HostSelectionPolicy = gocql.DCAwareRoundRobinPolicy(localDc)
cluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(
gocql.DCAwareRoundRobinPolicy(localDc),
)
}

var password string
Expand All @@ -145,14 +163,15 @@ func NewCassandraClient(conf *configuration.Config, testOnly bool) (*CassandraCl
}

if isSslEnabled {
tlsConfig, err := loadCassandraTLSConfig(dbconf, dbdriver)
insecureSkipVerify := dbconf.GetBoolean("tls.insecure_skip_verify")
tlsConfig, err := loadCassandraTLSConfig(dbconf, dbdriver, insecureSkipVerify)
if err != nil {
return nil, common.NewError(err)
}

sslOpts := &gocql.SslOptions{
Config: tlsConfig,
EnableHostVerification: false,
EnableHostVerification: !insecureSkipVerify,
}

cluster.SslOpts = sslOpts
Expand Down Expand Up @@ -197,20 +216,19 @@ func NewCassandraClient(conf *configuration.Config, testOnly bool) (*CassandraCl
// loadCassandraTLSConfig loads TLS configuration for Cassandra connection.
// Returns a tls.Config with certificates loaded from the configuration.
// The function expects tls.{} block under the database driver config (cassandra or yugabyte).
func loadCassandraTLSConfig(dbconf *configuration.Config, dbdriver string) (*tls.Config, error) {
// Check insecure_skip_verify flag first
insecureSkipVerify := dbconf.GetBoolean("tls.insecure_skip_verify")

func loadCassandraTLSConfig(dbconf *configuration.Config, dbdriver string, insecureSkipVerify bool) (*tls.Config, error) {
// Load client certificates for mTLS if provided (optional when insecure_skip_verify is true)
certFile := dbconf.GetString("tls.cert_file")
keyFile := dbconf.GetString("tls.key_file")
caCertFile := dbconf.GetString("tls.ca_cert_file")
serverName := dbconf.GetString("tls.server_name")

// Create TLS config for Cassandra connection.
// Prefer modern ECDHE+AEAD suites for forward secrecy; keep TLS_RSA_WITH_AES_128_CBC_SHA
// last as a fallback for legacy Cassandra 3.11.x nodes that only negotiate that suite.
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: serverName,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
Expand Down
Loading