-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Allow TLS connections in the driver #673
Conversation
It's supported elsewhere in the codebase, but currently the driver has no way to flag/indicate TLS through the DSN. This uses additional parameters / query strings to indicate ssl. I'm not sure _at all_ at the moment how to do the more complex custom tlsConfig, but as an inital commit we can try doing basic TLS. I could be being naive, but to me using `url.Parse` rather than this custom parsing code would be easier. But perhaps there a reason why it's done this way and why `?` is used instead of `/` for the database. I've done my changes to fit in with the current way of doing things.
I.e. if we just want to pass in the CA cert.
This was just so I could test something locally. I hardcoded the CA cert and domain I needed in and then had a custom TLS config which used the CA cert and domain only. The `make([]byte, 0)` are effectively Nils and a way to set the cert/key pair to Nil. Ideally what I'd like to do is finish this off properly, but I don't actually need this change. The basic TLS changes are all I need.
This is pretty rudimentary, but good enough to start a PR and review/discussion for how it should end up. - There is just a single custom config called `custom` - A program using the driver can only have one custom config. That "works for me", but for people using the driver in a single program to communicate with different endpoints that probably isn't going to work - Adds a basic "wrapper" function `SetCustomTLSConfig` which passes through to `NewClientTLSConfig`. This means just the driver can be imported. To use this you'd do: ``` import ( "database/sql" "net/url" "github.com/go-mysql-org/go-mysql/driver" // full import required ) [...] var ( CaPem = []byte(`-----BEGIN CERTIFICATE----- [...] -----END CERTIFICATE-----`) ) [...] // CA and domain, no Cert and Key driver.SetCustomTLSConfig(CaPem, make([]byte, 0), make([]byte, 0), false, "my.domain.com") [...] ```
I consider Go 100% wrong in not indenting switch/case.
Do you have time to take a look @skoef ? seems you are an active user of it 😄 |
I consider myself an active (or perhaps heavy?) user of go-mysql indeed, but haven't touched the For the DSN part, I think @atomicules raises a fair question: why is the database formatted as a query parameter where this is "always" the path parameter in the DSN. Not only would that make switching between drivers a lot easier but also parsing the DSN could be handled pretty much if not completely by |
I mostly argue on this decision, I think it's not much work to use a map to support multiple certificates. But I'm also ok if you want to change it in following pr. My desire API for certificates is
|
I will look at doing this.
I'll have a think about this as well to see if there is a nice way to not break existing use cases, but also allow a Thanks for the comments so far. |
Using changes per here: go-mysql-org/go-mysql#673
Stored in a map using the address (hostname:port) as the key. Initial work at addressing review feedback. Possibly/probably there is mutex and locking stuff we want to do. Maybe overkill though? Using the address vs the full DSN makes sense to me as the key. It's a little cleaner and I can't see a situation where a slight difference in the DSN (i.e. different database) would require a different certificate or TLS config. This is actually pretty neat as we just use `&ssl=custom` and it looks up the TLS Config (that people will have to pre-register/store) based on the address in the DSN.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@skoef do you want to give an advice?
Address feedback from review. The old style of DSN uses a `?` as path separator. This made parsing the DSN more complicated than necessary especially when adding in parsing of url parameters. By switching to the more standard `/` as a path separator we can use `url.Parse` to more easily split up the DSN and pull out the query strings. However... for compatibility we should still allow the old style. So what this commit does is: 1. Use a regex to identify whether a legacy or standard DSN style. We are assuming that if a "/" occurs after an "@" and no more "@" or "/" occur after that then it's the new/standard DSN style 2. Prefix the DSN with a scheme so we can use `url.Parse` 3. Only supports the url parameters (`ssl=X`) for the newer style DSN I.e. this shouldn't break anything for existing use cases, but does also allow people to start using TLS in the driver. There are probably edge cases in parsing of DSNs, but there would have been in the old way of doing it as well.
If we introduce changes to the way the URL can be parsed, what I would do before I start: abstract the code currently parsing the URL to a separate function and create a bunch of unit tests for it. This way you can know for sure you didn't mess up the original way of parsing whilst introducing new features. |
Hey, sorry for the long delay in getting back to this. I've just pushed two commits to address previous feedback (and I see you've already started leaving feedback).
|
I'll try to address the latest feedback a bit more quickly. |
Address linting failure: ``` Error return value of `errors.Errorf` is not checked (errcheck) ```
1. Remove comment about "extend the use of url.Parse" because that was in fact done in commit 53c3b91 2. Add godoc style comment explaining `SetCustomTLSConfig`
Review feedback. Needed to split out to add a unit test and that naturally resulted in adding a struct/type for the connection info. Ideally I'd go back in time and split out the original code and use this unit test for that - and maybe that'll happen if deemed necessary with some rebasing. But if not we should be able to satisfy ourselves that the changes to the driver parsing code are ok with this test.
@skoef Good point, but since I'd already started I've split out the new parsing code to a separate function and added a unit test after the fact. If deemed necessary I could rebase things and alter the git history, etc so that I split out the existing/original code and add a unit test for that. Alternatively we could just add DSN examples into the tests for the new parsing code. |
I had trouble installing/running locally. Working now.
Added some more commits:
From my point of view just
|
I've not noticed any data race issues, but just to be a bit safer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rest lgtm.
yes I hope we can add an unit test for original parsing code to remind us about the compatibility.
@skoef Do you have other comments?
|
||
"github.com/go-mysql-org/go-mysql/client" | ||
"github.com/go-mysql-org/go-mysql/mysql" | ||
"github.com/pingcap/errors" | ||
"github.com/siddontang/go/hack" | ||
) | ||
|
||
var customTLSMutex sync.Mutex |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var customTLSMutex sync.Mutex | |
var customTLSMutex sync.Mutex | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed this.
As long as we're backwards compatible ánd moving towards primarily using |
Sorry I missed this comment and the word "rebase" in later comment. I think we do not need it. Will merge this PR if you have nothing else to do. @atomicules |
Thanks for the approval. I'll wait for comments from @skoef before merging
I've not pushed up a commit, but what I could do is add code to func TestOriginalParseDSN(t *testing.T) {
// List of DSNs to test and expected results
// Use different numbered domains to more readily see what has failed - since we
// test in a loop we get the same line number on error
testDSNs := map[string]connInfo{
"user:password@localhost?db": connInfo{standardDSN: false, addr: "localhost", user: "user", password: "password", db: "db", params: url.Values(nil)},
"[email protected]?db": connInfo{standardDSN: false, addr: "1.domain.com", user: "user", password: "", db: "db", params: url.Values(nil)},
}
for supplied, expected := range testDSNs {
actual, err := originalParseDSN(supplied)
if err != nil {
t.Errorf("TestOriginalParseDSN failed. Got error: %s", err)
}
// Compare that with expected
if !reflect.DeepEqual(actual, expected) {
t.Errorf("TestOriginalParseDSN failed.\nExpected:\n%#v\nGot:\n%#v", expected, actual)
}
}
}
// originalParseDSN is from: https://github.com/go-mysql-org/go-mysql/blob/7153c75fcadc4083dcff2e772c4d4c906eccdeb3/driver/driver.go
func originalParseDSN(dsn string) (connInfo, error) {
ci := connInfo{}
lastIndex := strings.LastIndex(dsn, "@")
seps := []string{dsn[:lastIndex], dsn[lastIndex+1:]}
if len(seps) != 2 {
return ci, errors.Errorf("invalid dsn, must user:password@addr[?db]")
}
if ss := strings.Split(seps[0], ":"); len(ss) == 2 {
ci.user, ci.password = ss[0], ss[1]
} else if len(ss) == 1 {
ci.user = ss[0]
} else {
return ci, errors.Errorf("invalid dsn, must user:password@addr[?db]")
}
if ss := strings.Split(seps[1], "?"); len(ss) == 2 {
ci.addr, ci.db = ss[0], ss[1]
} else if len(ss) == 1 {
ci.addr = ss[0]
} else {
return ci, errors.Errorf("invalid dsn, must user:password@addr[?db]")
}
return ci, nil
} If we wanted something in there for reference? That would be instead of re-writing git history to add in before the changes. |
😄 I just wrote the comment above before seeing your comment. I am happy for it to be merged as is. |
Thank you! |
Opening this for discussion and feedback. I'm not expecting this to be merged in as-is.
This mostly exposes what is already elsewhere in this codebase to the driver
Allows for an optional
ssl=
URL parameter (query string) of valuestrue
andcustom
. Database must be specified for this to worktrue
just doesc.UseSSL(true)
on the connectioncustom
uses a package level variable ofcustomTLSConfig
to doc.SetTLSConfig(customTLSConfig)
That variable is set via a new function,
SetCustomTLSConfig
To use a custom config you'd do a full import of the driver (not for side-effects only) and then do something like:
For what I need the basic TLS support is enough (
ssl=true
andc.UseSSL(true)
) so if need be I could reduce this PR down to the first commit; However, since we are basically exposing what is already supported elsewhere in this codebase to the driver I thought I should get the custom TLS config support is a usable state (this PR).Things that I could do with feedback/opinions on:
Since the DSN style for this driver uses a
?
as a separator/indicator for the database thessl
parameter only works and makes sense if a database is specified. E.g:vs
Which would try to connect to a database called
ssl=true
.I am ok with this limitation/requirement.
I have also tried to keep the current style for parsing the URL parameters as opposed to re-working everything to use
url.Parse
. I assume there is a reason why that's not used and why?
is used instead of/
for the database separator/indicator; At the very least I understand that switching to/
would be a breaking change for many folkI've allowed the Cert and Key to be optional in
NewClientTLSConfig
. I think that is a good idea, but I don't know if I've gone about it the best wayI'm just allowing for a single
custom
TLSConfig. This means that if a program uses the driver then only one custom config can be set - which might not be desirable if that driver is connecting to multiple endpoints. For me, right now, that's fine. But I'm willing to look at this some moreSetCustomTLSConfig
is a very simple pass-through function. I welcome suggestions on how to improve. But it means I can just import the driver and still take advantage ofNewClientTLSConfig