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

GUACAMOLE-1289: Resolve relogin issue, remove spring dependency from Duo Auth, and update dependencies with vulnerabilities. #973

Closed
wants to merge 1 commit into from

Conversation

aleitner
Copy link
Contributor

@aleitner aleitner commented Apr 11, 2024

The merge of corresponding upstream #966 to main resulted in a bunch of dependabot warnings over old, vulnerable dependencies getting pulled in:
image

There is also an issue where logging out and then logging back in would result in an error thrown. This was due to query strings being checked to see if we were resuming authentication or not. I have added a new variable that is tracked within the Credentials to know if we are actually resuming authentication.

We also removed the spring requirement from Duo Auth when building a uri with specific query strings.

@aleitner aleitner marked this pull request as draft April 12, 2024 02:41
@aleitner aleitner force-pushed the GUACAMOLE-1289-dependencies branch from f064ec0 to 6708115 Compare April 12, 2024 03:47
@aleitner
Copy link
Contributor Author

Added a fix for the relogin bug mentioned in the previous Pull request #966 (comment)

@aleitner aleitner marked this pull request as ready for review April 12, 2024 03:51
@jmuehlner
Copy link
Contributor

jmuehlner commented Apr 12, 2024

Please update the PR title to reflect that it's also fixing the re-login bug.

@aleitner aleitner changed the title GUACAMOLE-1289: Remove spring dependency and update dependencies with vulnerabilities. GUACAMOLE-1289: Remove spring dependency from Duo Auth and update dependencies with vulnerabilities. Apr 15, 2024
@aleitner aleitner changed the title GUACAMOLE-1289: Remove spring dependency from Duo Auth and update dependencies with vulnerabilities. GUACAMOLE-1289: Resolve relogin issue, remove spring dependency from Duo Auth, and update dependencies with vulnerabilities. Apr 15, 2024
@aleitner aleitner force-pushed the GUACAMOLE-1289-dependencies branch from 6708115 to b91dfe9 Compare April 15, 2024 22:57
…cy and update dependencies with vulnerabilities.
@aleitner aleitner force-pushed the GUACAMOLE-1289-dependencies branch from b91dfe9 to 282a911 Compare April 15, 2024 23:24
*
* @return True if authentication is resumed, otherwise false.
*/
public Boolean isAuthenticationResumed() {
Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be nice if this could avoid going in Credentials, because it's pretty specific to Duo, and it's also not even the only way of resuming sessions - see the SSO (SAML, etc) extensions, which have their own completely-different mechanism for resuming interrupted auth attempts.

Maybe the DUO extension could do something similar? (using a URL parameter to track state). If there could be some shared functionality for this, it would be ideal, though that seems unlikely considering how different the implementations are.

At the bare minimum, I think that the SSO extensions should at least set this flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea was to migrate sso to this method

Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like the DUO extension is already tracking state through URL parameters.

Copy link
Contributor Author

@aleitner aleitner Apr 16, 2024

Choose a reason for hiding this comment

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

I tried to create a generic system for resumable authentication based on query strings set by the authentication provider. The query strings are defined by the authentication provider modules so that we can avoid accidental overlap.

/**
* The name of the parameter that will be used in the GET call-back that
* contains the session state.
*/
public static final String DUO_STATE_PARAMETER_NAME = "state";

Here is the order of events:

  1. The user attempts to authenticate with the primary application.
  2. If additional authentication is needed (e.g. Duo), the user is temporarily redirected to an external service.
  3. The primary application throws an error containing the necessary information to resume the authentication session and redirects to the external authentication service.
    throw new TranslatableGuacamoleInsufficientCredentialsException(
    "Verification using Duo is required before authentication "
    + "can continue.", "LOGIN.INFO_DUO_AUTH_REQUIRED",
    new CredentialsInfo(Collections.singletonList(
    new RedirectField(
    DUO_CODE_PARAMETER_NAME,
    new URI(duoClient.createAuthUrl(username, duoState)),
    new TranslatableMessage("LOGIN.INFO_DUO_REDIRECT_PENDING")
    )
    )),
    duoState, DuoAuthenticationProvider.PROVIDER_IDENTIFER,
    DUO_STATE_PARAMETER_NAME, expirationTimestamp
    );
  4. The primary application catches the error, stores the relevant information in a map for retrieval, and marks the credentials so that we know they need to be resumed.
    String state = e.getState();
    long expiration = e.getExpires();
    String queryIdentifier = e.getQueryIdentifier();
    String providerIdentifier = e.getProviderIdentifier();
    credentials.setAuthenticationResumed(true);
    resumableStateMap.put(state, new ResumableAuthenticationState(providerIdentifier,
    queryIdentifier, expiration, credentials));
  5. The external authentication service performs its task (verifying the user's identity) and then redirects the user back to the primary application with special query string parameters that indicate the result of the authentication process.
  6. The primary application handles the redirected request. It checks the query string parameters for a specific state parameter that matches a known "resumable state."
    private Credentials resumeAuthentication(Credentials credentials) {
    Credentials resumedCredentials = null;
    // Retrieve signed State from the request
    HttpServletRequest request = credentials.getRequest();
    // Retrieve the provider id from the query parameters
    String resumableProviderId = request.getParameter(Credentials.RESUME_QUERY);
    // Check if a provider id is set
    if (resumableProviderId == null || resumableProviderId.isEmpty()) {
    // Return if a provider id is not set
    return null;
    }
    // Use an iterator to safely remove entries while iterating
    Iterator<Map.Entry<String, ResumableAuthenticationState>> iterator = resumableStateMap.entrySet().iterator();
    while (iterator.hasNext()) {
    Map.Entry<String, ResumableAuthenticationState> entry = iterator.next();
    ResumableAuthenticationState resumableState = entry.getValue();
    // Check if the provider ID from the request matches the one in the map entry
    boolean providerMatches = resumableProviderId.equals(resumableState.getProviderIdentifier());
    if (!providerMatches) {
    // If the provider doesn't match, skip to the next entry
    continue;
    }
    // Use the query identifier from the entry to retrieve the corresponding state parameter
    String stateQueryParameter = resumableState.getQueryIdentifier();
    String stateFromParameter = request.getParameter(stateQueryParameter);
    // Check if a state parameter is set
    if (stateFromParameter == null || stateFromParameter.isEmpty()) {
    // Remove and continue if`state is not provided or is empty
    iterator.remove();
    continue;
    }
    // If the key in the entry (state) matches the state parameter provided in the request
    if (entry.getKey().equals(stateFromParameter)) {
    // Remove the current entry from the map
    iterator.remove();
    // Check if the resumableState has expired
    if (!resumableState.isExpired()) {
    // Set the actualCredentials to the credentials from the matched entry
    resumedCredentials = resumableState.getCredentials();
    if (resumedCredentials != null) {
    resumedCredentials.setRequest(request);
    }
    }
    // Exit the loop since we've found the matching state and it's unique
    break;
    }
    }
    return resumedCredentials;
    }
  7. On the primary application side, these states, along with information such as provider identifiers and credentials, are stored in a map. This allows the application to keep track of which authentication state corresponds to which user session.
  8. If a matching state is found and the state has not expired, the primary application uses the stored credentials and information to resume the user's authentication process.

Copy link
Contributor

Choose a reason for hiding this comment

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

The query strings are defined by the authentication provider modules so that we can avoid accidental overlap

FWIW, the ULR parameter for SAML is also called "state". Likely things won't work if people try to use DUO + SAML together. Might want to consider changing one or both of those.

Were you planning on migrating the SSO extensions to use this new flag in this PR?

Copy link
Contributor Author

@aleitner aleitner Apr 17, 2024

Choose a reason for hiding this comment

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

You're correct that the SAML extension also uses a state parameter, which could lead to confusion when both DUO and SAML are used concurrently, but I also added the provider_id as a required query parameter for resuming authentication. We don't have control over the name of the state query parameter received from DUO so I figured it was best to have the provider_id act as a namespacing mechanism to prevent collision. When redirecting from duo the query parameters will look like such: provider_id=duo&state=abc123&duo_code=abc123.

This PR isn't meant for migrating the SSO extensions but their was the plan to eventually migrate them to use the same code for resuming authentication.

Copy link
Contributor

Choose a reason for hiding this comment

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

It definitely feels like we shouldn't have the SSO extensions unmigrated for very long ... what do you think @mike-jumper ? Should migration of the SSO extensions to this new system be part of this ticket?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would agree that the SSO extension migration should be with this ticket - either this PR, or another PR to follow quickly after this one...

@mike-jumper
Copy link
Contributor

Closing in favor of recently-merged #980.

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