vapor / postgres-nio

🐘 Non-blocking, event-driven Swift client for PostgreSQL.
https://api.vapor.codes/postgresnio/documentation/postgresnio/
MIT License
317 stars 72 forks source link

SSL/TLS Handshake fails to AWS hosted postgres DB #315

Closed Sn0wfreezeDev closed 1 year ago

Sn0wfreezeDev commented 1 year ago

Describe the bug

Not sure if this bug is caused by SwiftNIO TLS or by the postgres-nio connection. I have a DB running on AWS (over Heroku) and the Vapor app receives the necessary info on how to connect to the DB over an environment variable, but I also verified this by hardcoding the DB credentials on macOS.

When I try to create a DB connection I use this code:

        self.tlsConfig = try NIOSSLContext(configuration: .makeClientConfiguration())
        let config = PostgresConnection.Configuration(
            connection: .init(
                host: host,
                port: port
            ),
            authentication: .init(
                username: String(username),
                database: db,
                password: String(password)
            ),
            tls: .require(tlsConfig!))

            let connection = try await PostgresConnection.connect(
                        on: eventLoop,
                        configuration: config,
                        id: 1,
                        logger: logger)

This is exactly the same code as the one that is implemented in the test suite here.

When I try to create the connection, I receive an SSL validation error from SwiftNIO. If I use the config from the test suite, it works just fine. Even with the same TLS settings. To my AWS instance I am only able to connect, if I disable certificate validation.

Here's the error that I receive:

2022-10-21T13:57:49.848229+00:00 app[web.1]: [ DEBUG ] Cleaning up and closing connection. [psql_connection_id: 1, psql_error: PSQLError(base: PostgresNIO.PSQLError.Base.connectionError(underlying: NIOSSL.NIOSSLError.handshakeFailed(NIOSSL.BoringSSLError.sslError([Error: 268435581 error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED at /tmp/build_ca2f10d3/.build/checkouts/swift-nio-ssl/Sources/CNIOBoringSSL/ssl/handshake.cc:393]))))] (PostgresNIO/New/PostgresChannelHandler.swift:480)
2022-10-21T13:57:49.848953+00:00 app[web.1]: [ ERROR ] Could not connect via postgres The operation could not be completed. (PostgresNIO.PSQLError error 1.) (App/configure.swift:30)

To Reproduce

I would not like to publicly share the full credentials of the DB server (even if this one is just for development). But I can share the AWS URL, so can verify the issue when entering some made up credentials.

AWS DB Server URL: ec2-54-228-218-84.eu-west-1.compute.amazonaws.com

Steps to reproduce the behavior:

  1. Add the Postgres package
  2. Try creating a connection using the code below
  3. See error
    func setupTestConnection(app: Application) async {
        let logger = Logger(label: "postgres-logger")

        do {
            let config = PostgresConnection.Configuration(
                connection: .init(
                    host: "ec2-54-228-218-84.eu-west-1.compute.amazonaws.com",
                    port: 5432
                ),
                authentication: .init(
                    username: "Test",
                    database: "db",
                    password: "Definitely not the right password"
                ),
                tls: .require(try .init(configuration: .makeClientConfiguration())))

            let eventLoopGroup = app.eventLoopGroup
            let eventLoop = eventLoopGroup.next()

            let connection = try await PostgresConnection.connect(
                on: eventLoop,
                configuration: config,
                id: 1,
                logger: logger)

            try await connection.close()
        }catch {
            logger.error(.init(stringLiteral: "ERROR:\n\(error)"))
        }
    }

Expected behavior

A connection to the server should not result in an SSL error. Since the server is managed by AWS, the SSL certificate should be right. Running an evaluation of the certificate with openssl in the command line works fine:

openssl s_client -connect ec2-54-228-218-84.eu-west-1.compute.amazonaws.com:543

Environment

0xTim commented 1 year ago

AWS databases use self-signed certificates, you need to pass a root cert or node cert to trust in the TLS configuration. You can find where to get these at https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html (may be different if it's managed by Heroku)

Sn0wfreezeDev commented 1 year ago

Okay, then I did an error when trying to verify the cert with the openssl CLI. Heroku does not mention that the certificates are self-signed or signed by an untrusted CA. For Node.js they require that unauthorized certificates are not rejected. To me this sounds a bit unsafe. The URL might have a solution to the problem. Thanks for that 😊

Here's the documentation from Heroku: https://devcenter.heroku.com/articles/connecting-heroku-postgres#connection-permissions

That's the Node.js Config:

ssl: {
    rejectUnauthorized: false
  }
0xTim commented 1 year ago

There's an example of a workaround in the docs, but the best way is to get a cert to trust from Heroku. I'm going to close this as it's an issue with Heroku rather than the library but feel free to reopen if anything else comes up!