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

[KeyVault] Challenge based authentication parallel fix, including tests #9059

Merged
merged 16 commits into from
May 27, 2020

Conversation

sadasant
Copy link
Contributor

@sadasant sadasant commented May 21, 2020

Fixes #9005

The challenge based authentication hotfix introduces a bug in which: Either when the client is not authenticated, or when the token is invalid, parallel network requests end up in a race condition where while one request is authenticating, the other ones fail immediately. This is not a problem before the hotfix because the challenge based authentication was always re-authenticating before making a request.

The parallel problem in more detail is as follows:

A new token was generated only if response.status == 401, and we received a www_authenticate header, which is ok, but parallel requests would choke at the !challenge.equalTo(this.challengeCache.challenge) mark, since by the time the second request compared the challenges, they would be the same, but the "response" set at the end was the 401 one, and not a new request made with the valid challenge. The fix consists of re-loading the credentials, and re-sending the request if the challenge happens to be valid at that point.

TODOs:

  • DONE: Do the same for Secrets and Certificates (should I do this in separate PRs?).
  • Do this same process in the hotfix branches for KeyVault.

Keep in mind that this PR is not about refactoring the entirety of the challenge based authentication, which I'll be doing separately, and I'm tracking it with this issue: #9058

Update: Decided to refactor the challenge auth here, to make it easier to test it. Therefore:

Fixes #9058

@sadasant sadasant added Client This issue points to a problem in the data-plane of the library. KeyVault labels May 21, 2020
@sadasant sadasant requested review from sophiajt and xirzec May 21, 2020 10:14
@sadasant sadasant self-assigned this May 21, 2020
@sadasant sadasant requested a review from ramya-rao-a May 21, 2020 13:59
@sadasant sadasant marked this pull request as ready for review May 21, 2020 13:59
@sadasant sadasant changed the title Challenge based auth fix, including a test [KeyVault-Keys] Challenge based auth fix, including a test May 21, 2020
@ramya-rao-a ramya-rao-a requested a review from daviwil May 21, 2020 16:18
/src/core/*
Copy link
Member

Choose a reason for hiding this comment

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

Why the root slash? Does that still always resolve to the root? The old line didn't have it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without the root hash, I can't re-include the challengeBasedAuth file that is in the code folder :/ I will clean this up once we move this to a common folder.

Copy link
Member

Choose a reason for hiding this comment

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

huh, why are we ignoring these files anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's the common folder PR that I'm proposing: #8866

Copy link
Contributor Author

@sadasant sadasant May 21, 2020

Choose a reason for hiding this comment

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

@xirzec the files in the src/core folder should only come from the swagger tool we use, which makes it so that any changes we make are futile :/ ... except for the challenge based authentication code! which we should move out of there.

Copy link
Member

Choose a reason for hiding this comment

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

ohh... can we rename the folder to 'generated' at some point to make this clear?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's an issue: #9069

import { Constants } from "@azure/core-http";
import { HttpOperationResponse } from "@azure/core-http";
import { HttpHeaders } from "@azure/core-http";
import { WebResource } from "@azure/core-http";
import { AccessTokenCache, ExpiringAccessTokenCache } from "@azure/core-http";

type ValidParsedWWWAuthenticateProperties =
| "authorization"
| "authorization_url"
Copy link
Member

Choose a reason for hiding this comment

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

In .NET, we have this as "authorization_uri". Are you sure it's "_url"? Adding @schaabs who wrote that originally.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm sure that's my mistake. I'll move to authorization_uri. Thank you!

Copy link
Member

Choose a reason for hiding this comment

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

If you're sure. I'm not so sure myself, which is why I included Scott. I'm not sure how to verify it as well. :)

Copy link
Member

@schaabs schaabs May 21, 2020

Choose a reason for hiding this comment

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

The authorization_uri was most likely copied from the track 1 code. If we're not sure probably best to default to what was in .NET track 1 since that SDK predates the rest. Either way this might just be a moot point left over from an older API version we're not supporting. Looking at the test recordings 7.0 seems to pass back "autorization" consistently in the challenge

        "WWW-Authenticate": "Bearer authorization=\u0022https:\u002f\u002flogin.windows.net\*****, resource=\u0022https:\u002f\u002fvault.azure.net\u0022",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good!

// or if the cached challenge has a different scope,
// we store the just received challenge and reset the cached token, to force a re-authentication.
// This is exactly what C# is doing, as we can see here:
// https://github.com/heaths/azure-sdk-for-net/blob/a35434c1ce955d4cdeb393748f9f6407d4a984e2/sdk/keyvault/Azure.Security.KeyVault.Shared/src/ChallengeBasedAuthenticationPolicy.cs#L233
Copy link
Member

Choose a reason for hiding this comment

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

I appreciate the nod, but might want to link to the main repo's source? 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I mean, I would get lost in the main repo's source! I want to link to what's similar in that code. Let me know if you know a better link.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wait, so you do compare the authority? hmm

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok! Updated!

Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

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

Small suggestion

/src/core/*
Copy link
Member

Choose a reason for hiding this comment

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

huh, why are we ignoring these files anyway?

@ramya-rao-a
Copy link
Contributor

Do the same for Secrets and Certificates (should I do this in separate PRs?).
Do this same process in the hotfix branches for KeyVault.

I would recommend changing secrets and certificates in the same PR for the master branch.

For hotfix, just like last time, we can use 1 PR for keys & secrets, another for certificates.

@sadasant sadasant changed the title [KeyVault-Keys] Challenge based auth fix, including a test [KeyVault-Keys] Challenge based authentication concurrency fix, including tests May 26, 2020
@sadasant sadasant changed the title [KeyVault-Keys] Challenge based authentication concurrency fix, including tests [KeyVault-Keys] Challenge based authentication parallel fix, including tests May 26, 2020
@sadasant
Copy link
Contributor Author

As a note on the wording, I'm using "parallel" since these promises are being executed at the same time, at least from the perspective of the developer. I'm using this as reference: https://takuti.me/note/parallel-vs-concurrent/

Let me know if I should change it.

@@ -0,0 +1,2 @@
# Ignoring the core files since they're auto-generated. Eventually, the auto-generated code will be on par with our current eslint rules, but in the mean time we should ignore them.
src/core
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This file was missing in certificates.

@ramya-rao-a ramya-rao-a requested a review from sophiajt May 26, 2020 15:48
@ramya-rao-a
Copy link
Contributor

@jonathandturner, @xirzec, Can you take another look now that the PR has been update to work with secrets and certificates as well?

@sadasant, Please update the PR title and have the hotfix PRs created as well

@sadasant sadasant changed the title [KeyVault-Keys] Challenge based authentication parallel fix, including tests [KeyVault] Challenge based authentication parallel fix, including tests May 26, 2020
Copy link
Member

@xirzec xirzec left a comment

Choose a reason for hiding this comment

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

I think this will work as is, but left a couple suggestions for further polish.

sdk/keyvault/keyvault-certificates/package.json Outdated Show resolved Hide resolved
// - An authorization URI with a token,
// - The resource to which that token is valid against (also called the scope).
const parsedWWWAuth = this.parseWWWAuthenticate(wwwAuthenticate);
const authorization = parsedWWWAuth.authorization!;
Copy link
Member

Choose a reason for hiding this comment

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

feels weird to do all these ! operations to assert something exists. If we know it exists, why make it optional on the type? If we don't know that it exists, why not add some checks so we don't blow up at runtime?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add an exception case if we don't receive the values that we expect! Thank you!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added this:

    if (!(authorization && resource)) {
      return this._nextPolicy.sendRequest(webResource);
    }

}
return response;
} else {
// If we don't receive a response with a 401 status code,
Copy link
Member

Choose a reason for hiding this comment

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

can everything from here until the end be moved up after the line webResource.body = originalBody inside the body of the if? Since we didn't blank the body in the else, we know it will never respond with 401 and we can just return the response.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we know it will never return 401? Seems like we are being extra cautious if we double check like this.

Copy link
Contributor Author

@sadasant sadasant May 27, 2020

Choose a reason for hiding this comment

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

Let's say we're already authenticated, but the server answers with 401 when our code tries to:

      await this.loadToken(webResource);
      response = await this._nextPolicy.sendRequest(webResource);

Seems possible! I'm assuming that if the challenge or token have some kind of expiration, this might help us correctly re-authenticate.

Copy link
Member

Choose a reason for hiding this comment

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

ah, sure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(I'm seeing an A-zure pun and I don't know what to do with it 😄)

@sadasant sadasant merged commit af3f6b3 into Azure:master May 27, 2020
@sadasant sadasant deleted the fix9005 branch May 27, 2020 21:55
mikeharder added a commit that referenced this pull request May 29, 2020
- Adds changes missing from #9059
@mikeharder mikeharder mentioned this pull request May 29, 2020
mikeharder added a commit that referenced this pull request May 29, 2020
- Adds changes missing from #9059
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Client This issue points to a problem in the data-plane of the library. KeyVault
Projects
None yet
6 participants