Commit b087dd1d authored by Yossi Gottlieb's avatar Yossi Gottlieb
Browse files

TLS: Connections refactoring and TLS support.

* Introduce a connection abstraction layer for all socket operations and
integrate it across the code base.
* Provide an optional TLS connections implementation based on OpenSSL.
* Pull a newer version of hiredis with TLS support.
* Tests, redis-cli updates for TLS support.
parent f4d37173
......@@ -4,6 +4,7 @@
# are preseved across iterations.
source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
test "Create a 5 nodes cluster" {
create_cluster 5 5
......@@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" {
--cluster-to $target \
--cluster-slots 100 \
--cluster-yes \
{*}[rediscli_tls_config "../../../tests"] \
| [info nameofexecutable] \
../tests/helpers/onlydots.tcl \
&] 0]
......
......@@ -5,6 +5,7 @@
# other masters have slaves.
source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
# Create a cluster with 5 master and 15 slaves, to make sure there are no
# empty masters and make rebalancing simpler to handle during the test.
......@@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" {
set output [exec \
../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=0 >@ stdout ]
}
test "Master #0 should lose its replicas" {
......@@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" {
set output [exec \
../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=.01 \
--cluster-use-empty-masters >@ stdout]
}
......
source tests/support/redis.tcl
source tests/support/util.tcl
set ::tlsdir "tests/tls"
# This function sometimes writes sometimes blocking-reads from lists/sorted
# sets. There are multiple processes like this executing at the same time
# so that we have some chance to trap some corner condition if there is
......@@ -8,8 +10,8 @@ source tests/support/util.tcl
# space to just a few elements, and balance the operations so that it is
# unlikely that lists and zsets just get more data without ever causing
# blocking.
proc bg_block_op {host port db ops} {
set r [redis $host $port]
proc bg_block_op {host port db ops tls} {
set r [redis $host $port 0 $tls]
$r select $db
for {set j 0} {$j < $ops} {incr j} {
......@@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} {
}
}
bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
source tests/support/redis.tcl
source tests/support/util.tcl
proc bg_complex_data {host port db ops} {
set r [redis $host $port]
set ::tlsdir "tests/tls"
proc bg_complex_data {host port db ops tls} {
set r [redis $host $port 0 $tls]
$r select $db
createComplexDataset $r $ops
}
bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
source tests/support/redis.tcl
proc gen_write_load {host port seconds} {
set ::tlsdir "tests/tls"
proc gen_write_load {host port seconds tls} {
set start_time [clock seconds]
set r [redis $host $port 1]
set r [redis $host $port 0 $tls]
$r select 9
while 1 {
$r set [expr rand()] [expr rand()]
......@@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} {
}
}
gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2]
gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
......@@ -17,6 +17,7 @@ source ../support/test.tcl
set ::verbose 0
set ::valgrind 0
set ::tls 0
set ::pause_on_error 0
set ::simulate_error 0
set ::failed 0
......@@ -69,7 +70,19 @@ proc spawn_instance {type base_port count {conf {}}} {
# Write the instance config file.
set cfgfile [file join $dirname $type.conf]
set cfg [open $cfgfile w]
puts $cfg "port $port"
if {$::tls} {
puts $cfg "tls-port $port"
puts $cfg "tls-replication yes"
puts $cfg "tls-cluster yes"
puts $cfg "port 0"
puts $cfg [format "tls-cert-file %s/../../tls/redis.crt" [pwd]]
puts $cfg [format "tls-key-file %s/../../tls/redis.key" [pwd]]
puts $cfg [format "tls-dh-params-file %s/../../tls/redis.dh" [pwd]]
puts $cfg [format "tls-ca-cert-file %s/../../tls/ca.crt" [pwd]]
puts $cfg "loglevel debug"
} else {
puts $cfg "port $port"
}
puts $cfg "dir ./$dirname"
puts $cfg "logfile log.txt"
# Add additional config files
......@@ -88,7 +101,7 @@ proc spawn_instance {type base_port count {conf {}}} {
}
# Push the instance into the right list
set link [redis 127.0.0.1 $port]
set link [redis 127.0.0.1 $port 0 $::tls]
$link reconnect 1
lappend ::${type}_instances [list \
pid $pid \
......@@ -148,6 +161,13 @@ proc parse_options {} {
set ::simulate_error 1
} elseif {$opt eq {--valgrind}} {
set ::valgrind 1
} elseif {$opt eq {--tls}} {
package require tls 1.6
::tls::init \
-cafile "$::tlsdir/ca.crt" \
-certfile "$::tlsdir/redis.crt" \
-keyfile "$::tlsdir/redis.key"
set ::tls 1
} elseif {$opt eq "--help"} {
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts "\nOptions:"
......@@ -492,7 +512,7 @@ proc restart_instance {type id} {
}
# Connect with it with a fresh link
set link [redis 127.0.0.1 $port]
set link [redis 127.0.0.1 $port 0 $::tls]
$link reconnect 1
set_instance_attrib $type $id link $link
......
......@@ -13,8 +13,9 @@ tags {"aof"} {
# cleaned after a child responsible for an AOF rewrite exited. This buffer
# was subsequently appended to the new AOF, resulting in duplicate commands.
start_server_aof [list dir $server_path] {
set client [redis [srv host] [srv port]]
set bench [open "|src/redis-benchmark -q -p [srv port] -c 20 -n 20000 incr foo" "r+"]
set client [redis [srv host] [srv port] 0 $::tls]
set bench [open "|src/redis-benchmark -q -s [srv unixsocket] -c 20 -n 20000 incr foo" "r+"]
after 100
# Benchmark should be running by now: start background rewrite
......@@ -29,7 +30,7 @@ tags {"aof"} {
# Restart server to replay AOF
start_server_aof [list dir $server_path] {
set client [redis [srv host] [srv port]]
set client [redis [srv host] [srv port] 0 $::tls]
assert_equal 20000 [$client get foo]
}
}
......@@ -52,7 +52,7 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv]
}
set client [redis [dict get $srv host] [dict get $srv port]]
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
test "Truncated AOF loaded: we expect foo to be equal to 5" {
assert {[$client get foo] eq "5"}
......@@ -69,7 +69,7 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv]
}
set client [redis [dict get $srv host] [dict get $srv port]]
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
assert {[$client get foo] eq "6"}
......@@ -170,7 +170,7 @@ tags {"aof"} {
}
test "Fixed AOF: Keyspace should contain values that were parseable" {
set client [redis [dict get $srv host] [dict get $srv port]]
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
......@@ -194,7 +194,7 @@ tags {"aof"} {
}
test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]]
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
......@@ -218,7 +218,7 @@ tags {"aof"} {
}
test "AOF+SPOP: Set should have 1 member" {
set client [redis [dict get $srv host] [dict get $srv port]]
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
......@@ -241,7 +241,7 @@ tags {"aof"} {
}
test "AOF+EXPIRE: List should be empty" {
set client [redis [dict get $srv host] [dict get $srv port]]
set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
......
......@@ -2,9 +2,9 @@
# Unlike stream operations such operations are "pop" style, so they consume
# the list or sorted set, and must be replicated correctly.
proc start_bg_block_op {host port db ops} {
proc start_bg_block_op {host port db ops tls} {
set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/bg_block_op.tcl $host $port $db $ops &
exec $tclsh tests/helpers/bg_block_op.tcl $host $port $db $ops $tls &
}
proc stop_bg_block_op {handle} {
......@@ -18,9 +18,9 @@ start_server {tags {"repl"}} {
set master_port [srv -1 port]
set slave [srv 0 client]
set load_handle0 [start_bg_block_op $master_host $master_port 9 100000]
set load_handle1 [start_bg_block_op $master_host $master_port 9 100000]
set load_handle2 [start_bg_block_op $master_host $master_port 9 100000]
set load_handle0 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
set load_handle1 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
set load_handle2 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
test {First server should have role slave after SLAVEOF} {
$slave slaveof $master_host $master_port
......
......@@ -18,6 +18,7 @@ start_server {} {
set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port]
set R_unixsocket($j) [srv [expr 0-$j] unixsocket]
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
}
......@@ -36,7 +37,7 @@ start_server {} {
}
set cycle_start_time [clock milliseconds]
set bench_pid [exec src/redis-benchmark -p $R_port(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &]
set bench_pid [exec src/redis-benchmark -s $R_unixsocket(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &]
while 1 {
set elapsed [expr {[clock milliseconds]-$cycle_start_time}]
if {$elapsed > $duration*1000} break
......
source tests/support/cli.tcl
start_server {tags {"cli"}} {
proc open_cli {} {
set ::env(TERM) dumb
set fd [open [format "|src/redis-cli -p %d -n 9" [srv port]] "r+"]
set cmdline [rediscli [srv port] "-n 9"]
set fd [open "|$cmdline" "r+"]
fconfigure $fd -buffering none
fconfigure $fd -blocking false
fconfigure $fd -translation binary
......@@ -54,8 +57,8 @@ start_server {tags {"cli"}} {
}
proc _run_cli {opts args} {
set cmd [format "src/redis-cli -p %d -n 9 $args" [srv port]]
foreach {key value} $opts {
set cmd [rediscli [srv port] [list -n 9 {*}$args]]
foreach {key value} $args {
if {$key eq "pipe"} {
set cmd "sh -c \"$value | $cmd\""
}
......
......@@ -29,6 +29,9 @@ start_server {tags {"repl"}} {
$slave slaveof $master_host $master_port
test {Slave enters handshake} {
if {$::tls} {
fail "TLS with repl-diskless-sync not supported yet."
}
wait_for_condition 50 1000 {
[string match *handshake* [$slave role]]
} else {
......@@ -184,6 +187,10 @@ start_server {tags {"repl"}} {
}
foreach mdl {no yes} {
if {$::tls && $mdl eq "yes"} {
puts "** Skipping test: TLS with repl-diskless-sync not supported yet."
continue
}
foreach sdl {disabled swapdb} {
start_server {tags {"repl"}} {
set master [srv 0 client]
......@@ -320,6 +327,9 @@ start_server {tags {"repl"}} {
}
test {slave fails full sync and diskless load swapdb recoveres it} {
if {$::tls} {
fail ""
}
start_server {tags {"repl"}} {
set slave [srv 0 client]
set slave_host [srv 0 host]
......@@ -387,6 +397,10 @@ test {slave fails full sync and diskless load swapdb recoveres it} {
}
test {diskless loading short read} {
if {$::tls} {
fail "TLS with repl-diskless-sync not supported yet."
}
start_server {tags {"repl"}} {
set replica [srv 0 client]
set replica_host [srv 0 host]
......
# Test conditions where an instance is considered to be down
source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
proc ensure_master_up {} {
wait_for_condition 1000 50 {
......@@ -28,7 +29,7 @@ test "Crash the majority of Sentinels to prevent failovers for this unit" {
test "SDOWN is triggered by non-responding but not crashed instance" {
lassign [S 4 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] host port
ensure_master_up
exec ../../../src/redis-cli -h $host -p $port debug sleep 10 > /dev/null &
exec ../../../src/redis-cli -h $host -p $port {*}[rediscli_tls_config "../../../tests"] debug sleep 10 > /dev/null &
ensure_master_down
ensure_master_up
}
......
proc rediscli_tls_config {testsdir} {
set tlsdir [file join $testsdir tls]
set cert [file join $tlsdir redis.crt]
set key [file join $tlsdir redis.key]
set cacert [file join $tlsdir ca.crt]
if {$::tls} {
return [list --tls --cert $cert --key $key --cacert $cacert]
} else {
return {}
}
}
proc rediscli {port {opts {}}} {
set cmd [list src/redis-cli -p $port]
lappend cmd {*}[rediscli_tls_config "tests"]
lappend cmd {*}$opts
return $cmd
}
......@@ -62,7 +62,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
lassign [split $ip_port :] start_host start_port
if {[catch {
set r {}
set r [redis $start_host $start_port]
set r [redis $start_host $start_port 0 $::tls]
set nodes_descr [$r cluster nodes]
$r close
} e]} {
......@@ -107,7 +107,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
# Connect to the node
set link {}
catch {set link [redis $host $port]}
catch {set link [redis $host $port 0 $::tls]}
# Build this node description as an hash.
set node [dict create \
......
......@@ -39,8 +39,17 @@ array set ::redis::callback {}
array set ::redis::state {} ;# State in non-blocking reply reading
array set ::redis::statestack {} ;# Stack of states, for nested mbulks
proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
set fd [socket $server $port]
proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0}} {
if {$tls} {
package require tls
::tls::init \
-cafile "$::tlsdir/ca.crt" \
-certfile "$::tlsdir/redis.crt" \
-keyfile "$::tlsdir/redis.key"
set fd [::tls::socket $server $port]
} else {
set fd [socket $server $port]
}
fconfigure $fd -translation binary
set id [incr ::redis::id]
set ::redis::fd($id) $fd
......@@ -48,6 +57,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
set ::redis::blocking($id) 1
set ::redis::deferred($id) $defer
set ::redis::reconnect($id) 0
set ::redis::tls $tls
::redis::redis_reset_state $id
interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
}
......@@ -72,7 +82,11 @@ proc ::redis::__dispatch__raw__ {id method argv} {
# Reconnect the link if needed.
if {$fd eq {}} {
lassign $::redis::addr($id) host port
set ::redis::fd($id) [socket $host $port]
if {$::redis::tls} {
set ::redis::fd($id) [::tls::socket $host $port]
} else {
set ::redis::fd($id) [socket $host $port]
}
fconfigure $::redis::fd($id) -translation binary
set fd $::redis::fd($id)
}
......
......@@ -92,7 +92,11 @@ proc is_alive config {
proc ping_server {host port} {
set retval 0
if {[catch {
set fd [socket $host $port]
if {$::tls} {
set fd [::tls::socket $host $port]
} else {
set fd [socket $host $port]
}
fconfigure $fd -translation binary
puts $fd "PING\r\n"
flush $fd
......@@ -136,7 +140,6 @@ proc tags {tags code} {
uplevel 1 $code
set ::tags [lrange $::tags 0 end-[llength $tags]]
}
proc start_server {options {code undefined}} {
# If we are running against an external server, we just push the
# host/port pair in the stack the first time
......@@ -145,7 +148,7 @@ proc start_server {options {code undefined}} {
set srv {}
dict set srv "host" $::host
dict set srv "port" $::port
set client [redis $::host $::port]
set client [redis $::host $::port 0 $::tls]
dict set srv "client" $client
$client select 9
......@@ -178,6 +181,13 @@ proc start_server {options {code undefined}} {
set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
set config {}
if {$::tls} {
dict set config "tls-cert-file" [format "%s/tests/tls/redis.crt" [pwd]]
dict set config "tls-key-file" [format "%s/tests/tls/redis.key" [pwd]]
dict set config "tls-dh-params-file" [format "%s/tests/tls/redis.dh" [pwd]]
dict set config "tls-ca-cert-file" [format "%s/tests/tls/ca.crt" [pwd]]
dict set config "loglevel" "debug"
}
foreach line $data {
if {[string length $line] > 0 && [string index $line 0] ne "#"} {
set elements [split $line " "]
......@@ -192,7 +202,17 @@ proc start_server {options {code undefined}} {
# start every server on a different port
set ::port [find_available_port [expr {$::port+1}]]
dict set config port $::port
if {$::tls} {
dict set config "port" 0
dict set config "tls-port" $::port
dict set config "tls-cluster" "yes"
dict set config "tls-replication" "yes"
} else {
dict set config port $::port
}
set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]]
dict set config "unixsocket" $unixsocket
# apply overrides from global space and arguments
foreach {directive arguments} [concat $::global_overrides $overrides] {
......@@ -254,10 +274,11 @@ proc start_server {options {code undefined}} {
}
# setup properties to be able to initialize a client object
set port_param [expr $::tls ? {"tls-port"} : {"port"}]
set host $::host
set port $::port
if {[dict exists $config bind]} { set host [dict get $config bind] }
if {[dict exists $config port]} { set port [dict get $config port] }
if {[dict exists $config $port_param]} { set port [dict get $config $port_param] }
# setup config dict
dict set srv "config_file" $config_file
......@@ -267,6 +288,7 @@ proc start_server {options {code undefined}} {
dict set srv "port" $port
dict set srv "stdout" $stdout
dict set srv "stderr" $stderr
dict set srv "unixsocket" $unixsocket
# if a block of code is supplied, we wait for the server to become
# available, create a client object and kill the server afterwards
......
......@@ -395,7 +395,7 @@ proc colorstr {color str} {
# of seconds to the specified Redis instance.
proc start_write_load {host port seconds} {
set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds &
exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds $::tls &
}
# Stop a process generating write load executed with start_write_load.
......@@ -423,7 +423,7 @@ proc lshuffle {list} {
# of ops to the specified Redis instance.
proc start_bg_complex_data {host port db ops} {
set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops $::tls &
}
# Stop a process generating write load executed with start_bg_complex_data.
......
......@@ -63,6 +63,7 @@ set ::all_tests {
unit/lazyfree
unit/wait
unit/pendingquerybuf
unit/tls
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
......@@ -71,6 +72,7 @@ set ::host 127.0.0.1
set ::port 21111
set ::traceleaks 0
set ::valgrind 0
set ::tls 0
set ::stack_logging 0
set ::verbose 0
set ::quiet 0
......@@ -92,6 +94,7 @@ set ::dont_clean 0
set ::wait_server 0
set ::stop_on_failure 0
set ::loop 0
set ::tlsdir "tests/tls"
# Set to 1 when we are running in client mode. The Redis test uses a
# server-client model to run tests simultaneously. The server instance
......@@ -146,7 +149,7 @@ proc reconnect {args} {
set host [dict get $srv "host"]
set port [dict get $srv "port"]
set config [dict get $srv "config"]
set client [redis $host $port]
set client [redis $host $port 0 $::tls]
dict set srv "client" $client
# select the right db when we don't have to authenticate
......@@ -166,7 +169,7 @@ proc redis_deferring_client {args} {
}
# create client that defers reading reply
set client [redis [srv $level "host"] [srv $level "port"] 1]
set client [redis [srv $level "host"] [srv $level "port"] 1 $::tls]
# select the right db and read the response (OK)
$client select 9
......@@ -204,7 +207,7 @@ proc test_server_main {} {
if {!$::quiet} {
puts "Starting test server at port $port"
}
socket -server accept_test_clients -myaddr 127.0.0.1 $port
socket -server accept_test_clients -myaddr 127.0.0.1 $port
# Start the client instances
set ::clients_pids {}
......@@ -450,6 +453,7 @@ proc print_help_screen {} {
"--stop Blocks once the first test fails."
"--loop Execute the specified set of tests forever."
"--wait-server Wait after server is started (so that you can attach a debugger)."
"--tls Run tests in TLS mode."
"--help Print this help screen."
} "\n"]
}
......@@ -486,6 +490,13 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
}
} elseif {$opt eq {--quiet}} {
set ::quiet 1
} elseif {$opt eq {--tls}} {
package require tls 1.6
set ::tls 1
::tls::init \
-cafile "$::tlsdir/ca.crt" \
-certfile "$::tlsdir/redis.crt" \
-keyfile "$::tlsdir/redis.key"
} elseif {$opt eq {--host}} {
set ::external 1
set ::host $arg
......@@ -565,7 +576,11 @@ if {[llength $::single_tests] > 0} {
}
proc attach_to_replication_stream {} {
set s [socket [srv 0 "host"] [srv 0 "port"]]
if {$::tls} {
set s [::tls::socket [srv 0 "host"] [srv 0 "port"]]
} else {
set s [socket [srv 0 "host"] [srv 0 "port"]]
}
fconfigure $s -translation binary
puts -nonewline $s "SYNC\r\n"
flush $s
......
start_server {tags {"limits"} overrides {maxclients 10}} {
if {$::tls} {
set expected_code "*I/O error*"
} else {
set expected_code "*ERR max*reached*"
}
test {Check if maxclients works refusing connections} {
set c 0
catch {
......@@ -12,5 +17,5 @@ start_server {tags {"limits"} overrides {maxclients 10}} {
} e
assert {$c > 8 && $c <= 10}
set e
} {*ERR max*reached*}
} $expected_code
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment