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

Fix redirect loop when oic credentials have expired but jenkins session is still valid #357

Merged
merged 5 commits into from
Jul 26, 2024

Conversation

mikecirioli
Copy link
Contributor

@mikecirioli mikecirioli commented Jul 23, 2024

to reproduce:

  1. configure a jenkins instance to auth using the plugin, do not enable refresh tokens
  2. assume your oic crentials lifetime is 120s (at least that is the case for my test provider)
  3. assume default skew allowance of 60s
  4. login to your jenkins instance
  5. notice that after only 60s, your OIC credentials have now expired!
  6. any subsequent attempt to access the instance results in a redirect loop between the provider and jenkins

Although the proposed PR fixes the issue AFAICT, I am not 100% confident it is the correct fix. This issue first appears in release 4.297.vcddb_d8a_e4694, but i have not been able to identify exactly what i believe changed to cause this behavior. My theory is that because the jenkins web session is still valid, it allows the request, which is then flagged as invalid because the OIC credentials have expired, kicking the whole loop off again. Hopefully you can confirm or deny this @krezovic

I also believe that the clock skew was mistakenly being subtracted from the credentials "expires in X seconds" calculation - my understanding was the clock skew should add an additional "buffer" to the lifetime of the credentials in order to accommodate slight variances in different clocks.

Testing done

Lots of manual testing....
I've also fixed 2 tests that i believe cover the changes made in this PR:

  • testRefreshTokenAndTokenExpiration_withoutRefreshToken()
    The test was originally using JenkinsRule.WebClient to check for a 302 respoonse when the OicCredentials are expired. Normally WebClient would follow redirects, and the test was only passing with the expected Blocked - 302 due to the redirect loop that was occurring. Fixing that caused this test to fail, so i've replaced that bit of test code with a java HttpClient set to not follow redirects.

  • the expire helper method was using the wrong number of ms to force the credentials to be expired. This apparently worked previously because the clock skew was actually being subtracted from the credential lifetime (instead of augmenting it). Since all we care about is ensuring the credentials appear expired I simply set the creation instant to 1 (ie. the first millisecond of time ever).

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests - that demonstrates feature works or fixes the issue

@mikecirioli mikecirioli requested a review from a team as a code owner July 23, 2024 14:10
@mikecirioli mikecirioli marked this pull request as draft July 23, 2024 14:20
@@ -1420,7 +1419,8 @@ public boolean handleTokenExpiration(HttpServletRequest httpRequest, HttpServlet
private void redirectOrRejectRequest(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
if (req.getSession(false) != null || Strings.isNullOrEmpty(req.getHeader("Authorization"))) {
WebApp.get(Jenkins.get().servletContext).getSomeStapler().invoke(req, res, Jenkins.get(), getLoginUrl());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@krezovic i was not familiar with this - was it essentially meant to perform a redirect?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mikecirioli I believe I was addressing feedback from the review where it was noted that it would be user-friendly to redirect user back to login when the token expires. Original implementation contained 401 only.

@mikecirioli mikecirioli marked this pull request as ready for review July 23, 2024 19:05
Copy link

codecov bot commented Jul 23, 2024

Codecov Report

Attention: Patch coverage is 33.33333% with 2 lines in your changes missing coverage. Please review.

Project coverage is 71.44%. Comparing base (b7205f1) to head (ad6b064).
Report is 3 commits behind head on master.

Files Patch % Lines
...va/org/jenkinsci/plugins/oic/OicSecurityRealm.java 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #357      +/-   ##
============================================
- Coverage     72.02%   71.44%   -0.58%     
+ Complexity      234      232       -2     
============================================
  Files            11       11              
  Lines           990      991       +1     
  Branches        142      142              
============================================
- Hits            713      708       -5     
- Misses          199      205       +6     
  Partials         78       78              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

webClient.assertFails(jenkins.getSecurityRealm().getLoginUrl(), 302);

// use an actual HttpClient to make checking redirects easier
HttpResponse<String> rsp = getPageWithGet("/manage");
Copy link
Member

Choose a reason for hiding this comment

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

Is JenkinsRule#createWebClient not enough for this?

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 the way the test was originally written - AFAICT JenkinsRule.WebClient follows http redirects, which was causing the test to fail. The test was passing before because JenkinsRule.WebClient was erroring out due to the redirect loop. I used the HttpClient because you can tell it not to follow redirects.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@amuniz just to be certain, i took a look at the original test and confirmed that it does indeed suffer from the same redirect loop
image

Copy link
Contributor

@michael-doubez michael-doubez left a comment

Choose a reason for hiding this comment

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

LGTM

@michael-doubez michael-doubez merged commit bba32e0 into jenkinsci:master Jul 26, 2024
16 of 18 checks passed
@mikecirioli
Copy link
Contributor Author

@michael-doubez i realize that i did not add a label to this PR before it was merged, and i don't have permissions to trigger a release - is that something you can do?

@michael-doubez
Copy link
Contributor

@mikecirioli my mistake - it didn't check

@@ -48,7 +48,7 @@ public OicCredentials(

if (expiresInSeconds != null && expiresInSeconds > 0) {
long allowedClockSkewFixed = Util.fixNull(allowedClockSkewSeconds, 60L);
this.expiresAtMillis = currentTimestamp + (expiresInSeconds - allowedClockSkewFixed) * 1000;
this.expiresAtMillis = (currentTimestamp + expiresInSeconds + allowedClockSkewFixed) * 1000;
Copy link
Contributor

Choose a reason for hiding this comment

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

This change seems to be incorrect. currentTimestamp is already in millis. Therefore, this line should be this.expiresAtMillis = currentTimestamp + (expiresInSeconds + allowedClockSkewFixed) * 1000.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants