redis / go-redis

Redis Go client
https://redis.uptrace.dev
BSD 2-Clause "Simplified" License
19.82k stars 2.33k forks source link

Unable to connect ClusterClient to Azure Cache for Redis #1698

Closed johannesvietze closed 3 years ago

johannesvietze commented 3 years ago

I'm unable to establish a connection to Azure Cache for Redis, when using the ClusterClient:

//this works, but I need a ClusterClient from now on...
client := redis.NewClient(&redis.Options{
        Addr:       config.Queue.Redis.Addr,
        Password:   config.Queue.Redis.Pw,
        DB:         config.Queue.Redis.Db,
        TLSConfig: &tls.Config{
            InsecureSkipVerify: false,
        },
    })
//this is giving me a i/o timeout error
client := redis.NewClusterClient(&redis.ClusterOptions{
        Addrs:    []string{config.Queue.Redis.Addr},
        Password: config.Queue.Redis.Pw,
    })
//this is giving me the error: x509: cannot validate certificate for xx.xxx.xx.xx because it doesn't contain any IP SANs
client := redis.NewClusterClient(&redis.ClusterOptions{
        Addrs:    []string{config.Queue.Redis.Addr},
        Password: config.Queue.Redis.Pw,
        TLSConfig: &tls.Config{
            InsecureSkipVerify:          false,
        },
    })

This issue might be related to #1691 ? Any ideas?

/edit: formatting /edit2: I'm using go-redis v8.6.0, tried on MacOS & Linux

johannesvietze commented 3 years ago

After further analysis I assume the standard Client correctly connects via DNS while the ClusterClient tries to connect via IP address... this causes issues because the Azure certificate is only valid for the hostname present in the Subject field of the certificate.

vmihailenco commented 3 years ago

@johannesvietze it uses the address returned by https://redis.io/commands/cluster-slots .

johannesvietze commented 3 years ago

Thanks for your help. I Just checked: executing cluster slots via redis-cli does indeed return the ip addresses, which do not match the Azure certificates...

What would be your recommendation to connect redis-go with the Azure Redis Cluster under these circumstances? The only possibility I see is to overwrite the NewClient func(opt *Options) *Client or ClusterSlots func(context.Context) ([]ClusterSlot, error) in the ClusterOptions...

vmihailenco commented 3 years ago

No ideas here. I would expect Azure Cache team to have some solution.

johannesvietze commented 3 years ago

I've opened a ticket with them and will post updates here.

In the meantime, I managed to work around it by passing in my own NewClient function and substituting the IP-address with the hostname.

johannesvietze commented 3 years ago

Response from azure support:

According to our Doc you will need to use hostname instead of IP to access Azure Redis (since this is PaaS based service) . As per document looks StackExchange.Redis you can use sslHost in the connection string when talk to the resource , not sure if go-redis has such feature or not. https://docs.microsoft.com/en-us/azure/azure-cache-for-redis/cache-how-to-premium-vnet#when-i-try-to-connect-to-my-azure-cache-for-redis-instance-in-a-virtual-network-why-do-i-get-an-error-stating-the-remote-certificate-is-invalid

and

As you known Azure Cache for Redis is a PaaS service and Azure LB will always be in front of the backend service which client application reaches first . So we need hostname for the LB to route the correct backend service to . Redis-cli only return server IP and you can stick with FQDN of the resource when format connection string (if using other redis client). IP based Cert is not a good practice considering if particular LB has issue the platform will dynamically failover FQDN to other healthy one which only work with SNI

monkey92t commented 3 years ago

@johannesvietze

you can try

redis.NewClusterClient(&redis.ClusterOptions{
                Addrs: []string{"..."},
                TLSConfig: &tls.Config{
                                ServerName: "you domain",
                },
})
johannesvietze commented 3 years ago

@johannesvietze

you can try

redis.NewClusterClient(&redis.ClusterOptions{
                Addrs: []string{"..."},
                TLSConfig: &tls.Config{
                                ServerName: "you domain",
                },
})

Awesome, that's the solution. Thank you!