vapor / redis

Vapor provider for RediStack
MIT License
458 stars 57 forks source link

TLS connection issues #216

Closed ramonschriks closed 2 months ago

ramonschriks commented 2 months ago

Describe the issue

Unable to connect with Redis master using TLS configuration

Vapor version

4.102.0

Operating system and version

MacOS 14.5

Swift version

Swift 5.10

Steps to reproduce

My situation:

  1. Setup a Redis master (im using valkey due to the closed sourcing of Redis)
    • Im using a local setup in Docker for development for this.
  2. Enable TLS on Redis master
  3. Setup self-signed certificates in Redis master
  4. Use client cert + CA used in Redis master to connect to Redis master using redis-cli $ redis-cli -h valkey-master -p 6379 -a laSQL2019 --tls --cacert 'ca.crt' --cert 'client.crt' --key 'client.key'

Connection gets established correctly using redis-cli from host machine to Redis master.

Now, on the same host machine im using the latest version of both Vapor and Vapor/redis to setup the redis configuration. Ive used many combinations trying to establish an connection:

guard let certFilePath = Environment.get("ENC_GATEWAY_REDIS_CERT_FILE"),
      let keyFilePath = Environment.get("ENC_GATEWAY_REDIS_PRIVATE_KEY_FILE"),
      let caFilePath = Environment.get("ENC_GATEWAY_REDIS_CA_FILE")
else {
    return nil
}

// Load certificates and keys
let clientCerts = try NIOSSLCertificate.fromPEMFile(certFilePath).map { NIOSSLCertificateSource.certificate($0) }
let clientKey = try NIOSSLPrivateKey(file: keyFilePath, format: .pem)
let caCert = try NIOSSLCertificate.fromPEMFile(caFilePath)

// Create TLS configuration
var configuration = TLSConfiguration.makeClientConfiguration()
configuration.certificateVerification = .none
configuration.trustRoots = .certificates(caCert)
configuration.additionalTrustRoots = [.certificates(caCert)]

configuration.certificateChain = clientCerts
configuration.privateKey = .privateKey(clientKey)

return try Redis.RedisConfiguration(
        serverAddresses: [masterAddress],
        password: self.configuration.password,
        tlsConfiguration: configuration
)

Ive tried many many different TLS configurations, but still i cannot connect. The connecties does gets logged on the Redis Master, but everytime this is the error:

Error accepting a client connection: error:0A00010B:SSL routines::wrong version number (addr=10.0.0.2:26094 laddr=10.0.0.53:6379)

I even tried adding the TLS version and cipher suite below, which does work when connecting using the Redis-cli, but not when configuring it in my Vapor app like so:

configuration.minimumTLSVersion = .tlsv12
configuration.cipherSuiteValues = [.TLS_AES_256_GCM_SHA384]

Maybe im doing something wrong on the Redis master regarding configuration, but then i wouldnt be able to connect using the redis-cli as well..

In almost any example i can find only, the option configuration.certificateVerification = .none should work to ignore verification (client side), but i always get the SSL routines::wrong version number error...

For now i just disable TLS server side (which is definitely not ideal), so hopefully im doing something wrong rather than this is a real issue.

Outcome

Error accepting a client connection: error:0A00010B:SSL routines::wrong version number (addr=10.0.0.2:26094 laddr=10.0.0.53:6379)

With TLS enabled on the Redis master, i always get the above error Server Side when establishing a connection. Using the redis-cli is does work with the same configuration (certs, TLS version and cipher suite)

Additional notes

No response

0xTim commented 2 months ago

This is probably an issue with https://github.com/swift-server/RediStack - have you tried just using that directly to ensure that's the cause?

ramonschriks commented 2 months ago

@0xTim Thanks for responding. I've tried that as well, but failed. Finding myself fighting this issue for over 10 hours straight now has driving me crazy.

Yet, i was able to find an (utmost simple) solution for this.

The problem was giving the masteraddress as SocketAddress object to the Redis Configuration together with the SSLConfiguration.

return try Redis.RedisConfiguration(
        serverAddresses: [masterAddress],
        password: self.configuration.password,
        tlsConfiguration: configuration
)

I changed it like so to make this work! As you can see below, im just providing the connection URL myself, by passing the correct protocol based on whether i have an TLS configuration or not.

let prot: String = self.configuration.tlsConfiguration != nil ? "rediss" : "redis"
return try Redis.RedisConfiguration(
    url: "\(prot)://:\(self.configuration.password)@\(host):\(port)",
    tlsConfiguration: self.configuration.tlsConfiguration,
    pool: .init(
        connectionRetryTimeout: .seconds(5)
    )
)

Im not sure if this is an issue within this lib or within RediStack, or if its even an issue? It seems to me that deriving the destination server details from the SocketAddress together with an TLS configuration does not results in a rediss:// connection string?

For now my issue has been resolved, tho it might be confusing when passing the address as SocketAddress.