Skip to content
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

Add SqlServer MSI Support #2

Merged
merged 10 commits into from
Jun 29, 2021
Merged

Add SqlServer MSI Support #2

merged 10 commits into from
Jun 29, 2021

Conversation

samfoxcode
Copy link
Owner

@samfoxcode samfoxcode commented Jun 25, 2021

Add the option for MSI Auth to Azure SQL Server. Expects a param to be passed to enable MSI Auth, useMsi=true.

  • update go-mssqldb module to pull in msi auth support
  • make code change in database/sqlserver/sqlserver.go to check for useMsi param
  • dynamically get database resource uri based on the server uri
  • adds adal module which is needed for fetching the msi token

PR from denisenkom/go-mssqldb that added msi support: #546

Add MSI Auth option to SQL Server connection. Default if no password is provided in connection string.
update host name parsing to get just the resource endpoint for msi
update go-mssqldb to resolve panic issue referenced here: denisenkom/go-mssqldb#642
switch from deprecated methods. NewServicePrincipalTokenFromManagedIdentity calls the two methods that are deprecated for us
add useMsi param instead of looking for nil password
Update sqlserver readme for msi auth. make useMsi a bit safer
remove comment

var db *sql.DB
if useMsi {
tokenProvider, err := getMSITokenProvider(fmt.Sprintf("%s%s", "https://", strings.Join(strings.Split(purl.Hostname(), ".")[1:], ".")))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this line be refactored into a helper in order to make its purpose more clear and maybe make it testable? I'm a little worried about some of these string operations being brittle with unexpected input.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I agree. The main purpose here is to get the resource uri which could change across clouds. I'll see what I can do. Currently, if the uri is incorrect, I believe it would result in a 4xx (invalid resource uri for tenant or something similar). Will validate that behavior and update

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, thank you! The cloud change use case is not something I had picked up from reading the code but that's super important.

var db *sql.DB
if useMsi {
tokenProvider, err := getMSITokenProvider(fmt.Sprintf("%s%s", "https://", strings.Join(strings.Split(purl.Hostname(), ".")[1:], ".")))
if err != nil {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, what happens if I pass useMsi as true but there's no provider available? Is it a timeout or an immediate failure?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the resource uri is invalid it'll timeout, but if there isn't an identity available it seems to just hang - looking into why

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! I'm trying to think like the maintainer of the upstream package might and try to identify any failure modes introduced. I don't think there's necessarily a specific action item here and we shouldn't block merging on this comment, but I am certainly curious.

return nil, err
}

db = sql.OpenDB(connector)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between this sql.OpenDB() call and the below sql.Open() call? What's the reason for using two different methods to open the database?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sql.Open will actually end up calling sql.OpenDB behind the scenes, but it builds the connector first based on what's passed in/the db context. sql.OpenDB just expects a connector, which is this case we're building ourselves, so we can call sql.OpenDB directly in that case. This follows the mssql guidance for using msi connections: mssql msi

source code which has sql.Open and sql.OpenDB (sorry, can't permalink to sql.Open): https://golang.org/src/database/sql/sql.go

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks for the explanation!

@@ -344,3 +374,16 @@ func (ss *SQLServer) ensureVersionTable() (err error) {

return nil
}

func getMSITokenProvider(resource string) (func() (string, error), error) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind briefly walking me through the decision to use a higher order function here? What is it buying us?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh, I see in this PR it's due to the short-lived nature of AAD tokens and this function ensures a refresh.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mssql.NewAccessTokenConnector expects a callback which is used to build the connection and that will be used to retrieve fresh tokens. msi.EnsureFresh() will update the token if it is about to expire, and then msi.OAuthToken() gets the token to be used for the connection

@kfcampbell
Copy link

I like these changes and I'm looking forward to being able to use them in the usage service! Managed identity SQL auth will save us a lot of headaches around rolling passwords and disaster recovery.

Do you have thoughts on the strategy to merge with the upstream repo after this merges in your branch? Some idle thoughts:

  • Their contributing docs aren't suuuper helpful.
  • Maybe it's best to merge here and then open up a PR in their repo.
  • @taywrobel works here and has committed to the project in the past. He might be able to help us or tell us what he did to get his CockroachDB changes in.

@samfoxcode
Copy link
Owner Author

I like these changes and I'm looking forward to being able to use them in the usage service! Managed identity SQL auth will save us a lot of headaches around rolling passwords and disaster recovery.

Do you have thoughts on the strategy to merge with the upstream repo after this merges in your branch? Some idle thoughts:

  • Their contributing docs aren't suuuper helpful.
  • Maybe it's best to merge here and then open up a PR in their repo.
  • @taywrobel works here and has committed to the project in the past. He might be able to help us or tell us what he did to get his CockroachDB changes in.

that was my initial thought too, have it reviewed here first then merge into the fork's main, then open a PR against the upstream

@kfcampbell
Copy link

That sounds good to me! I'm looking forward to seeing this PR on the main module. I won't "approve" this for now since I'm not quite sure of the etiquette in this fork scenario, but I'd definitely be interested in getting the maintainers' feedback soon.

refactor resource uri logic into its own method
add tests for msi connection. can only test whether it fails with useMsi= true, or succeeds with useMsi=false
@samfoxcode samfoxcode merged commit e898ae1 into master Jun 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants