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

Handling 403 status codes with "anonymous" #2163

Merged
merged 4 commits into from
Nov 16, 2020

Conversation

antgamdia
Copy link
Contributor

k8s api server nowadays defaults to allowing anonymous requests, so that rather than returning a 401, a 403 is returned.
Currently, we capture some 403 errors for displaying useful information (e.g., "user x doesn't have access to namespace y").
However, some edge cases remained unhandled. For instance, a user was able to 'bypass' UI authentication by simply entering " " (whitespace). Internally, it resulted in an anonymous authentication and the API returned 403 instead of 401.

Description of the change

This PR is to add a new handled 403 case so that the user gets redirected to the login page instead of viewing a meaningless error message.

Benefits

Users using tokens with expiration (i.e., #2143) will leverage from a better UX.

Possible drawbacks

I'm not fully aware of what possibly be the behavior when using OIDC. That's the reason why the anonymous 403 is only being handled when is not OIDC login.

Applicable issues

Additional information

N/A

 k8s api server nowadays defaults to allowing
 anonymous requests, so that rather than returning a 401, a 403 is returned.
@antgamdia antgamdia linked an issue Nov 16, 2020 that may be closed by this pull request
Copy link
Contributor

@andresmgot andresmgot left a comment

Choose a reason for hiding this comment

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

Thanks! I have a minor comment. Also, you are missing some tests to cover the changes.

Comment on lines 66 to 77
// A 403 directly from the auth proxy requires reauthentication.
if (Auth.usingOIDCToken() && Auth.is403FromAuthProxy(response)) {
dispatchErrorAndLogout(message);
return Promise.reject(new UnauthorizedError(message));
}
// If not using auth proxy, an anonymous response is sent when
// a serviceaccount token expires (eg., delete secret xxx)
// or the token is managed externally (eg., vsphere kubectl login)
// In this case, force reauthentication
if (!Auth.usingOIDCToken() && Auth.isAnonymous(response)) {
dispatchErrorAndLogout(message);
return Promise.reject(new UnauthorizedError(message));
Copy link
Contributor

Choose a reason for hiding this comment

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

these two ifs can be handled as one if is403FromAuthProxy || isAnonymous (the other comparison is irrelevant)

Also, why are you returning early? In theory message can be a JSON message (not sure for this use case) and it's handled later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I simply added another in case we should take other actions rather than logout. But, yes, if we gonna do the same in both alternatives we can merge them.

Regarding the rejection, I just thought that the logout action would be the last action and, consequently, there's no need to try to parse the JSON message (particularly, when it is an anonymous 403 it will always fail). But ok, I'll remove the early rejections just in case.

Copy link
Contributor

@andresmgot andresmgot left a comment

Choose a reason for hiding this comment

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

Thanks! I have just a couple of minor comments.

Comment on lines 103 to 109
return (
(response?.data?.message && response.data.message.includes("system:anonymous")) ||
(response?.data &&
response.data &&
typeof response.data === "string" &&
response.data.includes("system:anonymous"))
);
Copy link
Contributor

Choose a reason for hiding this comment

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

this condition is getting a bit messy :) Here is an alternative:

const msg = get(response, "data.message") || get(response, "data");
return typeof msg === "string" && msg.includes("system:anonymous");

(being get lodash.get)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not aware of lodash.get. Much simpler now, thanks for the tip!

Comment on lines 215 to 217
} catch (error) {
expect(store.getActions()).toEqual(expectedActions);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick, if the axios method doesn't throw the error you may get a false negative:

    try {
      await axios.get(testPath);
    } catch (error) {
      // compare here the error, if needed
    }
   expect(store.getActions()).toEqual(expectedActions);

@antgamdia antgamdia merged commit 9a7b52c into vmware-tanzu:master Nov 16, 2020
@antgamdia antgamdia deleted the 2143-anonymous403 branch November 16, 2020 18:08
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.

Kubeapps UI should gracefully handle token expiration
2 participants