From 848dfae7dc2cf39c87b37475ebcd3412410e9f24 Mon Sep 17 00:00:00 2001 From: hsshss Date: Mon, 12 Sep 2022 16:31:25 +0900 Subject: [PATCH 1/5] Add support insecure TLS connection --- main.go | 6 +++++- pkg/config/config.go | 2 ++ pkg/redisdump/tlsutils.go | 21 ++++++++++++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 7b57e1c..f733584 100644 --- a/main.go +++ b/main.go @@ -56,7 +56,11 @@ func realMain() int { var tlshandler *redisdump.TlsHandler = nil if c.Tls == true { - tlshandler = redisdump.NewTlsHandler(c.CaCert, c.Cert, c.Key) + tlshandler, err = redisdump.NewTlsHandler(c.CaCert, c.Cert, c.Key, c.Insecure) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + return 1 + } } var serializer func([]string) string diff --git a/pkg/config/config.go b/pkg/config/config.go index b507b12..3575501 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -18,6 +18,7 @@ type Config struct { Output string Silent bool Tls bool + Insecure bool CaCert string Cert string Key string @@ -52,6 +53,7 @@ func FromFlags(progName string, args []string) (Config, string, error) { flags.StringVar(&c.Output, "output", "resp", "Output type - can be resp or commands") flags.BoolVar(&c.Silent, "s", false, "Silent mode (disable logging of progress / stats)") flags.BoolVar(&c.Tls, "tls", false, "Establish a secure TLS connection") + flags.BoolVar(&c.Insecure, "insecure", false, "Allow insecure TLS connection by skipping cert validation") flags.StringVar(&c.CaCert, "cacert", "", "CA Certificate file to verify with") flags.StringVar(&c.Cert, "cert", "", "Private key file to authenticate with") flags.StringVar(&c.Key, "key", "", "SSL private key file path") diff --git a/pkg/redisdump/tlsutils.go b/pkg/redisdump/tlsutils.go index 0340104..3aa3804 100644 --- a/pkg/redisdump/tlsutils.go +++ b/pkg/redisdump/tlsutils.go @@ -3,26 +3,35 @@ package redisdump import ( "crypto/tls" "crypto/x509" + "errors" "fmt" "io/ioutil" ) type TlsHandler struct { + skipVerify bool caCertPath string certPath string keyPath string } -func NewTlsHandler(caCertPath, certPath, keyPath string) *TlsHandler { +func NewTlsHandler(caCertPath, certPath, keyPath string, insecure bool) (*TlsHandler, error) { if caCertPath == "" && certPath == "" && keyPath == "" { - return nil + if insecure { + return &TlsHandler{ + skipVerify: true, + }, nil + } else { + return nil, errors.New("no cert is set. if skip cert validation to set -insecure option") + } } return &TlsHandler{ + skipVerify: false, caCertPath: caCertPath, certPath: certPath, keyPath: keyPath, - } + }, nil } func tlsConfig(tlsHandler *TlsHandler) (*tls.Config, error) { @@ -30,6 +39,12 @@ func tlsConfig(tlsHandler *TlsHandler) (*tls.Config, error) { return nil, nil } + if tlsHandler.skipVerify { + return &tls.Config{ + InsecureSkipVerify: true, + }, nil + } + certPool := x509.NewCertPool() // ca cert is optional if tlsHandler.caCertPath != "" { From 103042137f0e152643dd06393996138bc3ce68c5 Mon Sep 17 00:00:00 2001 From: hsshss Date: Mon, 12 Sep 2022 16:28:26 +0900 Subject: [PATCH 2/5] Add support username and password authentication --- acceptance-tests/acceptance.bats | 5 ++++ acceptance-tests/redis-confs/users.acl | 2 ++ .../redis-confs/with_password.conf | 2 +- .../tests/select-db-with-username-password.sh | 29 +++++++++++++++++++ main.go | 1 + pkg/config/config.go | 2 ++ pkg/redisdump/redisdump.go | 11 +++++-- pkg/redisdump/redisdump_test.go | 12 +++++++- 8 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 acceptance-tests/redis-confs/users.acl create mode 100755 acceptance-tests/tests/select-db-with-username-password.sh diff --git a/acceptance-tests/acceptance.bats b/acceptance-tests/acceptance.bats index 48b170d..dff0aec 100644 --- a/acceptance-tests/acceptance.bats +++ b/acceptance-tests/acceptance.bats @@ -21,6 +21,11 @@ [ "$status" -eq 0 ] } +@test "Pass when using a non-default db, and a password with username" { + run tests/select-db-with-username-password.sh + [ "$status" -eq 0 ] +} + @test "Dumping / restoring all databases" { run tests/multiple-dbs.sh [ "$status" -eq 0 ] diff --git a/acceptance-tests/redis-confs/users.acl b/acceptance-tests/redis-confs/users.acl new file mode 100644 index 0000000..5a97261 --- /dev/null +++ b/acceptance-tests/redis-confs/users.acl @@ -0,0 +1,2 @@ +user default on >somepassword allkeys allchannels +@all +user test on >testpassword allkeys allchannels +@all diff --git a/acceptance-tests/redis-confs/with_password.conf b/acceptance-tests/redis-confs/with_password.conf index 5a1a9c8..f31f61a 100644 --- a/acceptance-tests/redis-confs/with_password.conf +++ b/acceptance-tests/redis-confs/with_password.conf @@ -1,2 +1,2 @@ port 6380 -requirepass somepassword \ No newline at end of file +aclfile /usr/local/etc/redis/users.acl \ No newline at end of file diff --git a/acceptance-tests/tests/select-db-with-username-password.sh b/acceptance-tests/tests/select-db-with-username-password.sh new file mode 100755 index 0000000..a3ba201 --- /dev/null +++ b/acceptance-tests/tests/select-db-with-username-password.sh @@ -0,0 +1,29 @@ +#!/bin/sh -e + +export DB=2 +export REDIS_PORT=6380 +export REDIS_USER=test +export REDISDUMPGO_AUTH=testpassword +export REDISCMD="redis-cli -h redis_secure -p $REDIS_PORT --user $REDIS_USER --pass $REDISDUMPGO_AUTH -n 2" +echo $REDISCMD +echo "-> Filling Redis with Mock Data..." +$REDISCMD FLUSHDB +/generator -output resp -type strings -n 100 | $REDISCMD --pipe +DBSIZE=`$REDISCMD dbsize` + +echo "-> Dumping DB..." +time /redis-dump-go -host redis_secure -n 250 -port $REDIS_PORT -db $DB -user $REDIS_USER -output resp >backup + +echo "-> Flushing DB and restoring dump..." +$REDISCMD FLUSHDB +$REDISCMD --pipe Comparing DB sizes..." +if [ $DBSIZE -ne $NEWDBSIZE ]; then + echo "ERROR - restored DB has $NEWDBSIZE elements, expected $DBSIZE" + exit 1 +else + echo "OK - $NEWDBSIZE elements" + exit 0 +fi diff --git a/main.go b/main.go index f733584..3e1fff5 100644 --- a/main.go +++ b/main.go @@ -112,6 +112,7 @@ func realMain() int { s := redisdump.Host{ Host: c.Host, Port: c.Port, + Username: c.Username, Password: redisPassword, TlsHandler: tlshandler, } diff --git a/pkg/config/config.go b/pkg/config/config.go index 3575501..cbacef7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -10,6 +10,7 @@ type Config struct { Host string Port int Db int + Username string Filter string Noscan bool BatchSize int @@ -45,6 +46,7 @@ func FromFlags(progName string, args []string) (Config, string, error) { flags.StringVar(&c.Host, "host", "127.0.0.1", "Server host") flags.IntVar(&c.Port, "port", 6379, "Server port") flags.IntVar(&c.Db, "db", -1, "only dump this database (default: all databases)") + flags.StringVar(&c.Username, "user", "", "Username") flags.StringVar(&c.Filter, "filter", "*", "Key filter to use") flags.BoolVar(&c.Noscan, "noscan", false, "Use KEYS * instead of SCAN - for Redis <=2.8") flags.IntVar(&c.BatchSize, "batchSize", 1000, "HSET/RPUSH/SADD/ZADD only add 'batchSize' items at a time") diff --git a/pkg/redisdump/redisdump.go b/pkg/redisdump/redisdump.go index 4bf6197..d889342 100644 --- a/pkg/redisdump/redisdump.go +++ b/pkg/redisdump/redisdump.go @@ -327,12 +327,16 @@ func RedisURL(redisHost string, redisPort string) string { return fmt.Sprintf("redis://%s:%s", redisHost, redisPort) } -func redisDialOpts(redisPassword string, tlsHandler *TlsHandler, db *uint8) ([]radix.DialOpt, error) { +func redisDialOpts(redisUsername string, redisPassword string, tlsHandler *TlsHandler, db *uint8) ([]radix.DialOpt, error) { dialOpts := []radix.DialOpt{ radix.DialTimeout(5 * time.Minute), } if redisPassword != "" { - dialOpts = append(dialOpts, radix.DialAuthPass(redisPassword)) + if redisUsername != "" { + dialOpts = append(dialOpts, radix.DialAuthUser(redisUsername, redisPassword)) + } else { + dialOpts = append(dialOpts, radix.DialAuthPass(redisPassword)) + } } if tlsHandler != nil { tlsCfg, err := tlsConfig(tlsHandler) @@ -385,6 +389,7 @@ func dumpDB(client radix.Client, db *uint8, filter string, nWorkers int, withTTL type Host struct { Host string Port int + Username string Password string TlsHandler *TlsHandler } @@ -396,7 +401,7 @@ func DumpServer(s Host, db *uint8, filter string, nWorkers int, withTTL bool, ba redisURL := RedisURL(s.Host, fmt.Sprint(s.Port)) getConnFunc := func(db *uint8) func(network, addr string) (radix.Conn, error) { return func(network, addr string) (radix.Conn, error) { - dialOpts, err := redisDialOpts(s.Password, s.TlsHandler, db) + dialOpts, err := redisDialOpts(s.Username, s.Password, s.TlsHandler, db) if err != nil { return nil, err } diff --git a/pkg/redisdump/redisdump_test.go b/pkg/redisdump/redisdump_test.go index 8b4863f..1300fc3 100644 --- a/pkg/redisdump/redisdump_test.go +++ b/pkg/redisdump/redisdump_test.go @@ -241,6 +241,7 @@ func TestParseKeyspaceInfo(t *testing.T) { func TestRedisDialOpts(t *testing.T) { for i, testCase := range []struct { + redisUsername string redisPassword string tlsHandler *TlsHandler db uint8 @@ -248,12 +249,21 @@ func TestRedisDialOpts(t *testing.T) { err error }{ { + "", "", nil, 1, 2, nil, }, { + "", + "test", + &TlsHandler{}, + 1, + 4, + nil, + }, { + "test", "test", &TlsHandler{}, 1, @@ -261,7 +271,7 @@ func TestRedisDialOpts(t *testing.T) { nil, }, } { - dOpts, err := redisDialOpts(testCase.redisPassword, testCase.tlsHandler, &testCase.db) + dOpts, err := redisDialOpts(testCase.redisUsername, testCase.redisPassword, testCase.tlsHandler, &testCase.db) if err != testCase.err { t.Errorf("expected error to be %+v, got %+v", testCase.err, err) } From b2cecc5fd449f74260d0ca26f0c51ea73142d657 Mon Sep 17 00:00:00 2001 From: Yann Hamon Date: Sat, 17 Sep 2022 12:30:01 +0200 Subject: [PATCH 3/5] Start multiple redis servers for acceptance tests of auth --- acceptance-tests/redis-confs/with_password.conf | 2 +- .../redis-confs/with_username_and_password.conf | 2 ++ .../tests/select-db-with-password.sh | 4 ++-- .../tests/select-db-with-username-password.sh | 4 ++-- docker-compose.yml | 17 ++++++++++++++--- 5 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 acceptance-tests/redis-confs/with_username_and_password.conf diff --git a/acceptance-tests/redis-confs/with_password.conf b/acceptance-tests/redis-confs/with_password.conf index f31f61a..280ebc0 100644 --- a/acceptance-tests/redis-confs/with_password.conf +++ b/acceptance-tests/redis-confs/with_password.conf @@ -1,2 +1,2 @@ port 6380 -aclfile /usr/local/etc/redis/users.acl \ No newline at end of file +requirepass somepassword diff --git a/acceptance-tests/redis-confs/with_username_and_password.conf b/acceptance-tests/redis-confs/with_username_and_password.conf new file mode 100644 index 0000000..f31f61a --- /dev/null +++ b/acceptance-tests/redis-confs/with_username_and_password.conf @@ -0,0 +1,2 @@ +port 6380 +aclfile /usr/local/etc/redis/users.acl \ No newline at end of file diff --git a/acceptance-tests/tests/select-db-with-password.sh b/acceptance-tests/tests/select-db-with-password.sh index 7fdd7aa..0ca98ba 100755 --- a/acceptance-tests/tests/select-db-with-password.sh +++ b/acceptance-tests/tests/select-db-with-password.sh @@ -3,7 +3,7 @@ export DB=2 export REDIS_PORT=6380 export REDISDUMPGO_AUTH=somepassword -export REDISCMD="redis-cli -h redis_secure -p $REDIS_PORT --pass $REDISDUMPGO_AUTH -n 2" +export REDISCMD="redis-cli -h redis_with_password -p $REDIS_PORT --pass $REDISDUMPGO_AUTH -n 2" echo "-> Filling Redis with Mock Data..." $REDISCMD FLUSHDB @@ -11,7 +11,7 @@ $REDISCMD FLUSHDB DBSIZE=`$REDISCMD dbsize` echo "-> Dumping DB..." -time /redis-dump-go -host redis_secure -n 250 -port $REDIS_PORT -db $DB -output resp >backup +time /redis-dump-go -host redis_with_password -n 250 -port $REDIS_PORT -db $DB -output resp >backup echo "-> Flushing DB and restoring dump..." $REDISCMD FLUSHDB diff --git a/acceptance-tests/tests/select-db-with-username-password.sh b/acceptance-tests/tests/select-db-with-username-password.sh index a3ba201..d9022b6 100755 --- a/acceptance-tests/tests/select-db-with-username-password.sh +++ b/acceptance-tests/tests/select-db-with-username-password.sh @@ -4,7 +4,7 @@ export DB=2 export REDIS_PORT=6380 export REDIS_USER=test export REDISDUMPGO_AUTH=testpassword -export REDISCMD="redis-cli -h redis_secure -p $REDIS_PORT --user $REDIS_USER --pass $REDISDUMPGO_AUTH -n 2" +export REDISCMD="redis-cli -h redis_with_username_and_password -p $REDIS_PORT --user $REDIS_USER --pass $REDISDUMPGO_AUTH -n 2" echo $REDISCMD echo "-> Filling Redis with Mock Data..." $REDISCMD FLUSHDB @@ -12,7 +12,7 @@ $REDISCMD FLUSHDB DBSIZE=`$REDISCMD dbsize` echo "-> Dumping DB..." -time /redis-dump-go -host redis_secure -n 250 -port $REDIS_PORT -db $DB -user $REDIS_USER -output resp >backup +time /redis-dump-go -host redis_with_username_and_password -n 250 -port $REDIS_PORT -db $DB -user $REDIS_USER -output resp >backup echo "-> Flushing DB and restoring dump..." $REDISCMD FLUSHDB diff --git a/docker-compose.yml b/docker-compose.yml index 650cf0f..5b26f05 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: image: "redis:alpine" ports: - "6379:6379" - redis_secure: + redis_with_password: image: "redis:alpine" volumes: - ./acceptance-tests/redis-confs:/usr/local/etc/redis @@ -15,6 +15,16 @@ services: protocol: tcp mode: host entrypoint: ["/usr/local/bin/redis-server", "/usr/local/etc/redis/with_password.conf"] + redis_with_username_and_password: + image: "redis:alpine" + volumes: + - ./acceptance-tests/redis-confs:/usr/local/etc/redis + ports: + - target: 6381 + published: 6381 + protocol: tcp + mode: host + entrypoint: ["/usr/local/bin/redis-server", "/usr/local/etc/redis/with_username_and_password.conf"] tests: image: "alpine:latest" volumes: @@ -23,6 +33,7 @@ services: - ./bin/redis-dump-go:/redis-dump-go depends_on: - "redis" - - "redis_secure" + - "redis_with_password" + - "redis_with_username_and_password" working_dir: /acceptance-tests - entrypoint: ["/acceptance-tests/entrypoint.sh"] \ No newline at end of file + entrypoint: ["/acceptance-tests/entrypoint.sh"] From 385fb7c6371cecda4c88dc7f6533b148ed6a10ac Mon Sep 17 00:00:00 2001 From: Yann Hamon Date: Sat, 17 Sep 2022 12:34:11 +0200 Subject: [PATCH 4/5] Add unit test case for passing username --- pkg/config/config_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index b6ed098..967e864 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -62,6 +62,20 @@ func TestFromFlags(t *testing.T) { Output: "commands", }, }, + { + []string{"-host", "redis", "-port", "1234", "-batchSize", "10", "-user", "test"}, + Config{ + Db: -1, + Host: "redis", + Port: 1234, + Filter: "*", + BatchSize: 10, + NWorkers: 10, + WithTTL: true, + Output: "resp", + Username: "test", + }, + }, { []string{"-db", "1"}, Config{ From e3c236319abe2b4aaa2e109fe431423bbd4dc431 Mon Sep 17 00:00:00 2001 From: Yann Hamon Date: Sat, 17 Sep 2022 12:37:36 +0200 Subject: [PATCH 5/5] Add unit tests around reading -insecure parameter --- pkg/config/config_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 967e864..444a651 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -21,6 +21,7 @@ func TestFromFlags(t *testing.T) { NWorkers: 10, WithTTL: true, Output: "resp", + Insecure: false, }, }, { @@ -34,6 +35,7 @@ func TestFromFlags(t *testing.T) { NWorkers: 10, WithTTL: true, Output: "resp", + Insecure: false, }, }, { @@ -47,6 +49,7 @@ func TestFromFlags(t *testing.T) { NWorkers: 10, WithTTL: false, Output: "resp", + Insecure: false, }, }, { @@ -60,10 +63,11 @@ func TestFromFlags(t *testing.T) { NWorkers: 5, WithTTL: true, Output: "commands", + Insecure: false, }, }, { - []string{"-host", "redis", "-port", "1234", "-batchSize", "10", "-user", "test"}, + []string{"-host", "redis", "-port", "1234", "-batchSize", "10", "-user", "test", "-insecure"}, Config{ Db: -1, Host: "redis", @@ -74,6 +78,7 @@ func TestFromFlags(t *testing.T) { WithTTL: true, Output: "resp", Username: "test", + Insecure: true, }, }, { @@ -87,6 +92,7 @@ func TestFromFlags(t *testing.T) { NWorkers: 10, WithTTL: true, Output: "resp", + Insecure: false, }, }, { @@ -101,6 +107,7 @@ func TestFromFlags(t *testing.T) { WithTTL: true, Output: "resp", Help: true, + Insecure: false, }, }, }