|
|
Chromium Code Reviews
Description[net/auth] Don't abort network transaction over non-permanent auth errors.
A multi-round authentication handshake may break partway through with an
error that indicates that the credentials used were invalid. With GSSAPI
we've seen this come up when the underlying library attempted to
authenticate against an endpoint even though no valid credentials were
available to finish the handshake. On Windows, this is now possible
since KB3189866.
Due to the fact that the underlying libraries attempt to start the
authentication handshake, the HttpNetworkTransaction proceeds past the
point where the HttpAuthController accepts the challenge and picks an
identity to use for the handshake. However, when the time comes to
generate a token, which happens just prior to sending the next HTTP
request, the HttpAuthController fails the operation with an
ERR_INVALID_AUTH_CREDENTIALS error. The state machine can't proceed past
this error and the user ends up looking at an error page.
e.g.:
C->S : GET something
S->C : HTTP/1.1 401 You shall not pass
WWW-Authenticate: Negotiate
C->[underlying authentication library, hereafter called UAL] :
"Can you authenticate to example.com?"
[UAL]->C: "Sure thing. Here's a token to get started : [token1]"
C->S : GET something
Authorization: Negotiate [token1]
S->C : HTTP/1.1 401 Need moar authentication
WWW-Authenticate: Negotiate [token2]
C->[UAL]: "example.com gave us [token2]. What should we do now?"
[UAL]->C: "LOL. Who knows? Look a squirrel!"
C: ...
C: Shows ERR_INVALID_AUTH_CREDENTIALS to the user.
This should be considered a permanent error if there is actually no
other way to proceed. However, if there are other authentication schemes
to try, or if the initial authentication attempt was made using ambient
credentials and the scheme supports explicit credentials, then those
should be attempted next.
This CL changes the response of the network stack at the final step to
restart the network transaction by sending a request with no
Authorization header. This signals to the server that the client is
restarting the authentication handshake. It can then start over at which
point the client can attempt to use a different identity or a different
authentication scheme to proceed.
R=mmenke
BUG=648366
Committed: https://crrev.com/e2257db89c38e2846d27a6de41a1ed4804ee5cab
Cr-Commit-Position: refs/heads/master@{#424563}
Patch Set 1 #
Total comments: 18
Patch Set 2 : Address comments #
Total comments: 5
Patch Set 3 : Improve testing #Patch Set 4 : Don't change auth_info_ lifetime. #Patch Set 5 : Add two hard fail test cases. #Patch Set 6 : Moar tests. #
Messages
Total messages: 33 (15 generated)
https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... File net/http/http_network_transaction_unittest.cc (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... net/http/http_network_transaction_unittest.cc:11554: {TestRound(kGet, kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, The implication here is a bit subtle. What we are looking for is whether the network transaction recovers from a permanent error by abandoning the auth handler and sending a request with no authorization headers for the target. In this case, this is verified by the second TestRound(kGet, [anything], OK) test round. This pattern is going to repeat in the test cases below. https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... net/http/http_network_transaction_unittest.cc:11582: {TestRound(kGet, kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, Note that here and elsewhere, the old test round looks like : TestRound(kGetAuth, ..., ERR_INVALID_AUTH_CREDENTIALS) and the new test round looks like TestRound(kGet, ..., OK). The kGetAuth mock write data *is not* relevant in the old code because the transaction aborts before sending that request. The kGet mock write data *is* relevant in the new code because the transaction doesn't abort, and instead continues without authorization headers.
The CQ bit was checked by asanka@chromium.org to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: This issue passed the CQ dry run.
I'll try to get to this today, but may not get to it until tomorrow - it's not a huge CL, but I have other stuff on my plate, and will need to wrap my head around auth state changes, which I'm not that familiar with.
On 2016/10/03 at 15:08:32, mmenke wrote: > I'll try to get to this today, but may not get to it until tomorrow - it's not a huge CL, but I have other stuff on my plate, and will need to wrap my head around auth state changes, which I'm not that familiar with. No worries. Thanks!
Sorry for all the questions, but I always have trouble figuring out how the auth classes plug in to each other. https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... File net/http/http_auth_controller.cc (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller.cc:165: OnIOComplete(rv); OnIOComplete calls HandleGenerateTokenResult itself, which seems weird...Also, won't callback be NULL at this point, so this OnIOComplete call effectively does nothing? Or am I missing something? Know this is old code, just trying to understand what's going on. https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller.cc:486: return OK; So this makes us send a new request, without a token, get a new challenge, and start the process over from scratch? What happens if the server hangs up on us when we retry? https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller.cc:489: case ERR_MISSING_AUTH_CREDENTIALS: We get ERR_INVALID_AUTH_CREDENTIALS instead of ERR_MISSING_AUTH_CREDENTIALS in the case you describe, where we get credentials from the system for the first round, but not for the second? https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... File net/http/http_auth_controller.h (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller.h:45: // were necessary. We're returning OK, but don't seem to be generating a token, and I don't think no token was necessary? https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... File net/http/http_auth_controller_unittest.cc (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller_unittest.cc:120: // ERR_INVALID_AUTH_CREDENTIALS is special. It's a non-permanet error, but Should we update the description of this in net_error_list.h? It's not obvious that this is a local error, not the server rejecting our credentials...Or can it be caused by both? https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... File net/http/http_network_transaction_unittest.cc (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... net/http/http_network_transaction_unittest.cc:4161: // credentials, but they shouldn't be used, since they were already tried. So default credentials for all different handlers are considered the same, even if the auth handlers are for different schemes? Or are all auth handlers always for the same scheme? https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... net/http/http_network_transaction_unittest.cc:4184: MockWrite data_writes2[] = { The first auth handler tries to generate credentials and fail, so we just retry? Maybe a comment about that? https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... net/http/http_network_transaction_unittest.cc:4200: "Authorization: auth_token\r\n\r\n"), What would happen differently if it tried default credentials?
https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... File net/http/http_auth_controller.cc (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller.cc:165: OnIOComplete(rv); On 2016/10/04 at 19:42:14, mmenke wrote: > OnIOComplete calls HandleGenerateTokenResult itself, which seems weird...Also, won't callback be NULL at this point, so this OnIOComplete call effectively does nothing? Or am I missing something? Know this is old code, just trying to understand what's going on. Thanks for catching this. I moved things around so it's clearer what happens. The OnIOComplete() callback wasn't doing anything. https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller.cc:486: return OK; On 2016/10/04 at 19:42:14, mmenke wrote: > So this makes us send a new request, without a token, get a new challenge, and start the process over from scratch? What happens if the server hangs up on us when we retry? Either an error displayed to the user, or depending on when the socket was found to be disconnected, we'll reconnect and proceed as if we saw a "Connection: close". Although I think I'm misunderstanding the question? https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller.cc:489: case ERR_MISSING_AUTH_CREDENTIALS: On 2016/10/04 at 19:42:14, mmenke wrote: > We get ERR_INVALID_AUTH_CREDENTIALS instead of ERR_MISSING_AUTH_CREDENTIALS in the case you describe, where we get credentials from the system for the first round, but not for the second? Initially we ask the system whether we can initiate an authentication handshake with the target server. We'd see a ERR_MISSING_AUTH_CREDENTIALS trickle down the system if the system believes we don't have credentials that can be used to authenticate with the target. ERR_INVALID_AUTH_CREDENTIALS is seen when the system attempts to use credentials, but discovers that the credentials cannot be used with the target partway through the handshake. The credentials aren't missing, they just were not valid for use with the target. https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... File net/http/http_auth_controller.h (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller.h:45: // were necessary. On 2016/10/04 at 19:42:14, mmenke wrote: > We're returning OK, but don't seem to be generating a token, and I don't think no token was necessary? Yeah, we'll need to update the documentation. It was always the case that for permanent errors we'll return OK with no token. The caller, in general, only wants to know whether the transaction can continue in the state the auth controller is in after MaybeGenerateAuthToken returns. The answer to that is yes. We may want to consider some other error code if we, for instance, want to force the transaction to restart with a new socket and mark the current socket as poisoned. That's a larger change. https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... File net/http/http_auth_controller_unittest.cc (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_auth_controll... net/http/http_auth_controller_unittest.cc:120: // ERR_INVALID_AUTH_CREDENTIALS is special. It's a non-permanet error, but On 2016/10/04 at 19:42:15, mmenke wrote: > Should we update the description of this in net_error_list.h? It's not obvious that this is a local error, not the server rejecting our credentials...Or can it be caused by both? It could be caused by both. In this specific bug, the credentials were rejected by the client (e.g. the server's challenge didn't have some property that's required by client policy). But it could be rejected by the server as well (e.g. the client didn't support some feature of the auth mechanism required by policy). https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... File net/http/http_network_transaction_unittest.cc (right): https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... net/http/http_network_transaction_unittest.cc:4161: // credentials, but they shouldn't be used, since they were already tried. On 2016/10/04 at 19:42:15, mmenke wrote: > So default credentials for all different handlers are considered the same, even if the auth handlers are for different schemes? Or are all auth handlers always for the same scheme? These are auth handlers for the same scheme. The state for which schemes and identities have been tried is maintained by the HttpAuthController. A HttpAuthHandler cannot be reused safely. So the controller throws out the existing handler and creates a new one. https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... net/http/http_network_transaction_unittest.cc:4184: MockWrite data_writes2[] = { On 2016/10/04 at 19:42:15, mmenke wrote: > The first auth handler tries to generate credentials and fail, so we just retry? Maybe a comment about that? Done. https://codereview.chromium.org/2382293004/diff/1/net/http/http_network_trans... net/http/http_network_transaction_unittest.cc:4200: "Authorization: auth_token\r\n\r\n"), On 2016/10/04 at 19:42:15, mmenke wrote: > What would happen differently if it tried default credentials? The type of credentials being used is checked by the following two assertions that follow the first RestartWithAuth() call: EXPECT_FALSE(trans->IsReadyToRestartForAuth()); EXPECT_TRUE(response->auth_challenge); IsReadyToRestartForAuth() false if the chosen identity needs explicit credentials (i.e. not using ambient / default credentials). auth_challenge would be non-null if an auth challenge is available to prompt the user. I added comments to point this out in the code below.
Sorry for the delay on this one - I want to better understand the tests. I'll start walking through them today, but may not get back to you until tomorrow.
Thanks for bearing. Think I have a reasonable handle on these now, just a couple questions about test coverage. https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... File net/http/http_network_transaction_unittest.cc (right): https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... net/http/http_network_transaction_unittest.cc:11561: {TestRound(kGet, kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, Should we still have a test that hard fails here? ERR_FAILED or something? Both sync and async. https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... net/http/http_network_transaction_unittest.cc:11561: {TestRound(kGet, kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, More typically, we'd see another TestRound(kGet, kServerChallenge, OK) after this, and send another set of credentials. Can this test fixture handle that? https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... net/http/http_network_transaction_unittest.cc:11570: {TestRound(kGet, kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, More typically, we'd see another TestRound(kGet, kServerChallenge, OK) after this, and then fail (I think?) since we've given up on the original scheme, guess this text fixture can't handle that?
Thanks for bearing with me, rather. On 2016/10/10 21:06:10, mmenke wrote: > Thanks for bearing. Think I have a reasonable handle on these now, just a > couple questions about test coverage. > > https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... > File net/http/http_network_transaction_unittest.cc (right): > > https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... > net/http/http_network_transaction_unittest.cc:11561: {TestRound(kGet, > kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, > Should we still have a test that hard fails here? ERR_FAILED or something? > Both sync and async. > > https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... > net/http/http_network_transaction_unittest.cc:11561: {TestRound(kGet, > kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, > More typically, we'd see another TestRound(kGet, kServerChallenge, OK) after > this, and send another set of credentials. Can this test fixture handle that? > > https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... > net/http/http_network_transaction_unittest.cc:11570: {TestRound(kGet, > kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, > More typically, we'd see another TestRound(kGet, kServerChallenge, OK) after > this, and then fail (I think?) since we've given up on the original scheme, > guess this text fixture can't handle that?
The CQ bit was checked by asanka@chromium.org to run a CQ dry run
PTAL? Made the test changes. https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... File net/http/http_network_transaction_unittest.cc (right): https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... net/http/http_network_transaction_unittest.cc:11561: {TestRound(kGet, kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, On 2016/10/10 at 21:06:09, mmenke wrote: > More typically, we'd see another TestRound(kGet, kServerChallenge, OK) after this, and send another set of credentials. Can this test fixture handle that? The test fixture will need some minor tweaking, but I'll add another test round. Specifically, it'll verify that the auth scheme doesn't get disabled and that it can respond to the subsequent challenge. https://codereview.chromium.org/2382293004/diff/20001/net/http/http_network_t... net/http/http_network_transaction_unittest.cc:11570: {TestRound(kGet, kServerChallenge, OK), TestRound(kGet, kSuccess, OK)}}, On 2016/10/10 at 21:06:10, mmenke wrote: > More typically, we'd see another TestRound(kGet, kServerChallenge, OK) after this, and then fail (I think?) since we've given up on the original scheme, guess this text fixture can't handle that? Added another round. The new test round verifies that the network transaction isn't waiting for to auth. Since there are no more auth schemes to try, the network transaction can't proceed past the challenge.
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
PS4 LGTM, modulo comment about the hard failures (Basically, where we had tests with ERR_INVALID_AUTH_CREDENTIALS failures before, we should presumably have tests with ERR_FAILED or ERR_UNEXPECTED or something, right? Or can we only get failures from a magic set that don't result in hard failures there?)
On 2016/10/11 at 20:02:19, mmenke wrote: > PS4 LGTM, modulo comment about the hard failures (Basically, where we had tests with ERR_INVALID_AUTH_CREDENTIALS failures before, we should presumably have tests with ERR_FAILED or ERR_UNEXPECTED or something, right? Or can we only get failures from a magic set that don't result in hard failures there?) Added hard failure test cases. Thanks!
The CQ bit was checked by asanka@chromium.org to run a CQ dry run
Dry run: CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
On 2016/10/11 20:14:19, asanka wrote: > On 2016/10/11 at 20:02:19, mmenke wrote: > > PS4 LGTM, modulo comment about the hard failures (Basically, where we had > tests with ERR_INVALID_AUTH_CREDENTIALS failures before, we should presumably > have tests with ERR_FAILED or ERR_UNEXPECTED or something, right? Or can we > only get failures from a magic set that don't result in hard failures there?) > > Added hard failure test cases. Thanks! LGTM. Though do we want/need proxy auth (tunnel/non-tunnel) hard fails, too?
The CQ bit was unchecked by commit-bot@chromium.org
Dry run: Try jobs failed on following builders: ios-simulator on master.tryserver.chromium.mac (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.mac/builders/ios-simulator/bui...)
The CQ bit was checked by asanka@chromium.org
The patchset sent to the CQ was uploaded after l-g-t-m from mmenke@chromium.org Link to the patchset: https://codereview.chromium.org/2382293004/#ps100001 (title: "Moar tests.")
CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
The CQ bit was unchecked by commit-bot@chromium.org
Try jobs failed on following builders: mac_chromium_rel_ng on master.tryserver.chromium.mac (JOB_FAILED, http://build.chromium.org/p/tryserver.chromium.mac/builders/mac_chromium_rel_...)
The CQ bit was checked by mmenke@chromium.org
CQ is trying da patch. Follow status at https://chromium-cq-status.appspot.com/v2/patch-status/codereview.chromium.or...
On 2016/10/11 at 20:15:43, mmenke wrote: > On 2016/10/11 20:14:19, asanka wrote: > > On 2016/10/11 at 20:02:19, mmenke wrote: > > > PS4 LGTM, modulo comment about the hard failures (Basically, where we had > > tests with ERR_INVALID_AUTH_CREDENTIALS failures before, we should presumably > > have tests with ERR_FAILED or ERR_UNEXPECTED or something, right? Or can we > > only get failures from a magic set that don't result in hard failures there?) > > > > Added hard failure test cases. Thanks! > > LGTM. Though do we want/need proxy auth (tunnel/non-tunnel) hard fails, too? Thanks. Yeah, I think I'm going to rework the testing here. It's quite unreadable as it is. I'll send you a followup.
Message was sent while issue was closed.
Committed patchset #6 (id:100001)
Message was sent while issue was closed.
Description was changed from
==========
[net/auth] Don't abort network transaction over non-permanent auth errors.
A multi-round authentication handshake may break partway through with an
error that indicates that the credentials used were invalid. With GSSAPI
we've seen this come up when the underlying library attempted to
authenticate against an endpoint even though no valid credentials were
available to finish the handshake. On Windows, this is now possible
since KB3189866.
Due to the fact that the underlying libraries attempt to start the
authentication handshake, the HttpNetworkTransaction proceeds past the
point where the HttpAuthController accepts the challenge and picks an
identity to use for the handshake. However, when the time comes to
generate a token, which happens just prior to sending the next HTTP
request, the HttpAuthController fails the operation with an
ERR_INVALID_AUTH_CREDENTIALS error. The state machine can't proceed past
this error and the user ends up looking at an error page.
e.g.:
C->S : GET something
S->C : HTTP/1.1 401 You shall not pass
WWW-Authenticate: Negotiate
C->[underlying authentication library, hereafter called UAL] :
"Can you authenticate to example.com?"
[UAL]->C: "Sure thing. Here's a token to get started : [token1]"
C->S : GET something
Authorization: Negotiate [token1]
S->C : HTTP/1.1 401 Need moar authentication
WWW-Authenticate: Negotiate [token2]
C->[UAL]: "example.com gave us [token2]. What should we do now?"
[UAL]->C: "LOL. Who knows? Look a squirrel!"
C: ...
C: Shows ERR_INVALID_AUTH_CREDENTIALS to the user.
This should be considered a permanent error if there is actually no
other way to proceed. However, if there are other authentication schemes
to try, or if the initial authentication attempt was made using ambient
credentials and the scheme supports explicit credentials, then those
should be attempted next.
This CL changes the response of the network stack at the final step to
restart the network transaction by sending a request with no
Authorization header. This signals to the server that the client is
restarting the authentication handshake. It can then start over at which
point the client can attempt to use a different identity or a different
authentication scheme to proceed.
R=mmenke
BUG=648366
==========
to
==========
[net/auth] Don't abort network transaction over non-permanent auth errors.
A multi-round authentication handshake may break partway through with an
error that indicates that the credentials used were invalid. With GSSAPI
we've seen this come up when the underlying library attempted to
authenticate against an endpoint even though no valid credentials were
available to finish the handshake. On Windows, this is now possible
since KB3189866.
Due to the fact that the underlying libraries attempt to start the
authentication handshake, the HttpNetworkTransaction proceeds past the
point where the HttpAuthController accepts the challenge and picks an
identity to use for the handshake. However, when the time comes to
generate a token, which happens just prior to sending the next HTTP
request, the HttpAuthController fails the operation with an
ERR_INVALID_AUTH_CREDENTIALS error. The state machine can't proceed past
this error and the user ends up looking at an error page.
e.g.:
C->S : GET something
S->C : HTTP/1.1 401 You shall not pass
WWW-Authenticate: Negotiate
C->[underlying authentication library, hereafter called UAL] :
"Can you authenticate to example.com?"
[UAL]->C: "Sure thing. Here's a token to get started : [token1]"
C->S : GET something
Authorization: Negotiate [token1]
S->C : HTTP/1.1 401 Need moar authentication
WWW-Authenticate: Negotiate [token2]
C->[UAL]: "example.com gave us [token2]. What should we do now?"
[UAL]->C: "LOL. Who knows? Look a squirrel!"
C: ...
C: Shows ERR_INVALID_AUTH_CREDENTIALS to the user.
This should be considered a permanent error if there is actually no
other way to proceed. However, if there are other authentication schemes
to try, or if the initial authentication attempt was made using ambient
credentials and the scheme supports explicit credentials, then those
should be attempted next.
This CL changes the response of the network stack at the final step to
restart the network transaction by sending a request with no
Authorization header. This signals to the server that the client is
restarting the authentication handshake. It can then start over at which
point the client can attempt to use a different identity or a different
authentication scheme to proceed.
R=mmenke
BUG=648366
Committed: https://crrev.com/e2257db89c38e2846d27a6de41a1ed4804ee5cab
Cr-Commit-Position: refs/heads/master@{#424563}
==========
Message was sent while issue was closed.
Patchset 6 (id:??) landed as https://crrev.com/e2257db89c38e2846d27a6de41a1ed4804ee5cab Cr-Commit-Position: refs/heads/master@{#424563} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
