-
Notifications
You must be signed in to change notification settings - Fork 67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Secure SSL defaults #61
Comments
Pinging @voltone, any guidance would be very appreciated :) |
The standard The assumption presumably is that for peer-to-peer connections you'd use something like PSK or PBKDF cipher suites, rather than certificates. It is possible to handle self-signed peer certificates by passing a custom :ssl.connect(
'localhost',
4433,
cacerts: [],
verify_fun: {fn
cert, {:bad_cert, :selfsigned_peer}, state ->
# Decide if `cert` is the expected certificate!
{:valid, state}
_, {:extension, _}, state ->
{:unknown, state}
end, nil}
) Couple of things to note:
The AWS Aurora issue is probably due to a wildcard certificate. If you are ok with supporting only OTP 21.0 or later you can enable wildcard certificate matching by passing the following ssl option: customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)] Properly supporting wildcard certs with older OTP versions is very hard without using the ssl_verify_fun dependency. |
I think it's reasonable to add ssl_verify_fun as optional dependency, if SSL is being used by myxql explicitly (e.g. However.. what about CA? There are certifi and castore. I'm not sure it's common for mysql server to use certificates signed by trusted CA. If most mysql client library/tools honor system CA (which usually includes trusted CA) - then it may make sense to use those trust CA by default |
@voltone and @chulkilee sorry for no-followups, thank you for your feedback. Given we don't necessarily want to require OTP 21+ yet or add ssl_verify_fun, we decided to warn when users leave |
FYI, the solution proposed by @voltone which make use of the following line does not work on AWS Aurora:
So it seem like the certificate being a wildcard is not the only validation error that pops (maybe the order at which the certificates are delivered in the TLS certificate exchange?). Luckily, like @wojtekmach mentioned, ssl_verify_fun works. I have done this testing using Postgres and not Mysql but I assume it makes no matter in our context. |
The other thing you'll likely have to set with AWS is the Does an error get logged when the connection fails? |
When trying with: ssl_opts = [
verify: :verify_peer,
cacertfile: "/path/to/rds-ca-2019-root.pem",
customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)],
depth: 3
]
{:ok, pid} = Postgrex.start_link(hostname: "database-2-instance-1.cwlgt8axnrkn.eu-west-1.rds.amazonaws.com", username: "postgres", password: "XXXXX", database: "postgres", ssl: true, ssl_opts: ssl_opts) I get the following:
Here are the 2 certificates delivered by the server during the handshake, as seen by wireshark: The CA cert file comes from https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html |
In Wireshark, the The maximum length allowed for the CN component is 64, so there should be enough room for the full hostname, but if the value is indeed truncated it might explain your issue. I'd have to dig a little deeper once I have the time... |
This is just a presentation issue. In Wireshark, when that node is expanded, I can see that the Subject is the full hostname. I guess the issue is a bit more complex. Any any case, thanks for your help. |
@voltone We've recently shipped being able to connect from any MySQL to PlanetScale (a cloud hosted Vitess / MySQL solution) and one of the first questions we got was Elixir support 😄. I tried to set it up, but I had to do a lot of complicated things sadly and the discussion in this issue is touching on a lot of these points I think.
One of the main issues is that I could not get it working even with the above explanation and hostname = "uxht6i2xcb2t.us-east-1.psdb.cloud"
{:ok, pid} = MyXQL.start_link(username: "zqz7nlrz80im",
database: "test",
hostname: hostname,
password: "pscale_pw_X",
port: 3306,
ssl: true,
ssl_opts: [
verify: :verify_peer,
cacertfile: CAStore.file_path(),
server_name_indication: String.to_charlist(hostname),
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
) I used The key bit though here, is this one: server_name_indication: String.to_charlist(hostname), The issue is that with MySQL an existing socket is upgraded to a TLS socket. This means it doesn't follow the regular path to create a TLS connection, where the hostname is provided in the connect call. Because the socket is already established at this point, it does not know about the original hostname that it was connecting with. This means that the @chaodhib I suspect the above fix will also solve this for AWS Aurora or for any database that provides a valid signed certificate with a hostname (I know Azure MySQL does as well for example). All in, this is quite complex compared to other drivers, so in the context of this issue title, I was wondering if you'd be willing to consider the following:
|
Unfortunately no one has, at least until now, managed to write a reliable and portable function for selecting the system CA trust store. It is not trivial to make this work across various Linux distributions, to say nothing of Mac / Windows. This is why most BEAM applications bring their own trust store in the form of a package ( |
I think in practice this is definitely doable, but I agree that it doesn't really belong in a driver. Take for example the approach from Go (see https://github.com/golang/go/blob/master/src/crypto/x509/root_linux.go) which has a list of filenames that are checked. It also works for Mac which exposes a single path. Windows is indeed a whole different game. But all this would ideally be something handled in Elixir / Erlang where it has to be written only once and it can be reused. For me the far more surprising bug here has been that |
To Postgrex users, I had to add the same magic sauce too. config :my_app, MyApp.Repo,
ssl: true,
ssl_opts: [
verify: :verify_peer,
cacerts: [database_ca_cert],
server_name_indication: '#{database_hostname}',
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
] |
@voltone cacerts/cacertfile aside for a second, do you think we can ship with these defaults? I'm fine with setting these only on OTP 21+ (or just requiring that release) In terms of cacerts/cacertfile, the landscape changed since OTP 25 ships with os-provided store. So we could use that or at the very least nudge people towards it (like Mint does elixir-mint/mint#354). To provide best user experience, I'd be OK with having an optional dependency on CAStore too, just like Mint does. @dbussink and @jayjun and others, thank you so much for looking into this. |
Until recently the default value for Some older OTP versions might have weak cipher suites enabled by default, but I think that's going to be harder to fix here. I suppose we should just encourage people to use a recent OTP versions if possible. |
@voltone great, thank you! |
@dbussink RE: hostname = "uxht6i2xcb2t.us-east-1.psdb.cloud"
{:ok, pid} = MyXQL.start_link(username: "zqz7nlrz80im",
database: "test",
hostname: hostname,
password: "pscale_pw_X",
port: 3306,
ssl: true,
ssl_opts: [
verify: :verify_peer,
cacertfile: CAStore.file_path(),
server_name_indication: String.to_charlist(hostname),
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
) fyi, with latest version, MyXQL v0.7.0, it is now: {:ok, pid} = MyXQL.start_link(
username: "zqz7nlrz80im",
database: "test",
hostname: "uxht6i2xcb2t.us-east-1.psdb.cloud",
password: "pscale_pw_X",
ssl: [:public_key.cacerts_get()]
) If you have Elixir docs somewhere it'd be nice to update them and if they are in a public repo, I'm happy to send a PR, lmk! (And sorry for the wait. 😅 ) |
We should ship with secure by default SSL configuration [1]. Currently we use OTP defaults.
I couldn't get this reliably to work, here's what I tried when setting
verify: :verify_peer
.First of all, self-signed certificates automatically created by MySQL doesn't seem to work with verification:
(https://dev.mysql.com/doc/refman/5.7/en/using-encrypted-connections.html)
I tried manually creating self-signed certs using [2] with different Common Name but no luck too. Same error:
Generating valid self-signed certificates is crucial to be able to run this on CI.
I then tried running tests against AWS Aurora (Provisioned, MySQL 5.7).
First, got protocol version error:
That instance only supports TLSv1 so we force that versions:
Finally, with ssl_verify_fun I was able to get this to work:
However, I'd prefer not to add a dependency.
[1] https://blog.voltone.net/post/23
[2] https://dev.mysql.com/doc/refman/8.0/en/creating-ssl-files-using-openssl.html
The text was updated successfully, but these errors were encountered: