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

chore: added migration step to migrate keytar secrets to vscode SecretStorage - VSCODE-435 #552

Merged
merged 17 commits into from
Jul 6, 2023

Conversation

himanshusinghs
Copy link
Contributor

@himanshusinghs himanshusinghs commented Jul 4, 2023

Description

In this PR, we added a migration step to migrate secrets one by one, earlier stored using keytar to VsCode's SecretStorage. The pseudo-code followed here is the following:

## Migration process
- Extension activates
- Initiate a call to fetch saved connections
  - Get all the connections from our connection storage
  - For each connection
    - perform the old connection modal to new connection info migration
    - return connection info merged with secrets
      - Check if we have `secretStorageLocation` key set in root of connection info object
      - Yes? (We attempted a migration already for this connection)
        - Is `secretStorageLocation` === `vscode.Keytar`? (We tried migrating but failed, keytar was unavailable)
          - Return the connection info without merging the secrets
        - Is `secretStorageLocation` === `vscode.SecretStorage`?
          - Fetch the secret from `vscode.SecretStorage`
          - Merge it with connection info and return the merged connection info
      - No? (The connection is yet to be migrated)
        - Check if keytar is available
        - No?
          - Set `secretStorageLocation` key to `vscode.Keytar`
          - Log as migration error that keytar is unavailable
          - Return the connection info as it is
        - Yes?
          - Check if credentials are in keytar
          - Yes?
            - Copy the credentials over to `vscode.SecretStorage`
            - Set `secretStorageLocation` key on connection info to `vscode.SecretStorage`
            - Return connection info merged with retrieved secrets
          - No?
            - Set `secretStorageLocation` key to `vscode.SecretStorageLocation`
            - Save '{}' to vscode.SecretStorageLocation for this connection
            - Return connection info merged with empty object for secret
  - Check if there were any connections that we could not migrate and notify the user of them and also track the telemetry event

If we fail to migrate for any connection, we notify the user using vscode's information message API. An example here:

image (2)

Checklist

Motivation and Context

  • Bugfix
  • New feature
  • Dependency update
  • Misc

Dependents

Caused by: #546
Resolves: #476

Types of changes

  • Backport Needed
  • Patch (non-breaking change which fixes an issue)
  • Minor (non-breaking change which adds functionality)
  • Major (fix or feature that would cause existing functionality to change)

src/test/suite/connectionController.test.ts Outdated Show resolved Hide resolved
src/storage/storageController.ts Outdated Show resolved Hide resolved
src/connectionController.ts Outdated Show resolved Hide resolved
src/connectionController.ts Outdated Show resolved Hide resolved
src/connectionController.ts Outdated Show resolved Hide resolved
src/connectionController.ts Outdated Show resolved Hide resolved
src/connectionController.ts Outdated Show resolved Hide resolved
connectionsWithSecretsInKeytar
.map(({ connectionName }) => connectionName)
.join(', '),
].join('\n')
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have any intuition about what the cause of this error would typically be in practice? What do we expect the user to do about this? Is there some kind of error message that we could bubble up to the user or a log that we could point them to?

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 one in particular will happen when the keytar shim will no longer be available for us to use(in the next public release of vscode). This is not actually an error but an error like condition which we plan to notify the users about using the vscode.showInformationMessage. Unfortunately the only action users can take about it is to remove the connection from the explorer view and add it again manually

We are not explicitly stating the same in the error message because I was not sure about it but I do think that it might be ok to call that out specifically in the message that we show. Iff we decide to show the message to remove the connection, then we might even just provide users a small button to purge connections that we could not migrate (because of keytar not available anymore).

Copy link
Contributor

Choose a reason for hiding this comment

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

The message here could be more descriptive saying that keytar is being removed from vscode and we tried to migrate connections, but some failed. @GaurabAryal, maybe the product team could help with better phrasing. I think this is important to explain what happened and the alternatives users have. I am not sure if listing connections by names will work well if users have many saved connections. Some of those can have duplicate default names like localhost. This is why my suggestion was to show a broken leaf icon for such connections to easier identify those for users and let them take corresponding action. For this PR the improved message would be enough, but maybe it would be also nice to have s follow-up PR with an improved UX.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea @GaurabAryal is in touch with designers to have the broken leaf prepared but until then maybe I will sit with him for a better copy.

Copy link
Contributor Author

@himanshusinghs himanshusinghs Jul 6, 2023

Choose a reason for hiding this comment

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

PR updated with the approved copy - here

src/connectionController.ts Outdated Show resolved Hide resolved

// We tried migrating this connection earlier but failed because Keytar was not
// available. So we return simply the connection without secrets.
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this happen when the user clicks away a request to unlock their OS keyring because they are repetitive/annoying if they have multiple connections stored? In that case it would be reasonable to assume that a subsequent attempt to get credentials would succeed, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, for such connections it would be safe to assume that they can be re-tried for migration.

There is one thing not so much clear to me - how / when would a user get a prompt to unlock the keyring? Asking this because it did not happen for me? Could be because I have been using the extension for sometime and might have allowed it at some point or could it be because keytar module does not prompt?

Copy link
Contributor

Choose a reason for hiding this comment

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

could it be because keytar module does not prompt?

keytar doesn’t do the prompting as far as I know, it integrates with the OS keychains instead, so when exactly the prompt occurs will depend on the OS and its configuration.

(I think we get prompts on macOS when upgrading Electron in Compass, for example. I’m not sure if something similar happens with VSCode at some point.)

Copy link
Contributor

Choose a reason for hiding this comment

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

It happened before for vscode as well but does not happen anymore for a year or so. Not sure if we did something or vscode handles it differently. @Anemy do you have any context about 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.

In that case - I would assume that the keytar module will still be available (unless it was removed) but the getPassword call should fail which should throw an error. For this reason we will never mark such connection as migrated and thus they should be good for re-trying. But I should to test that.

Copy link
Member

Choose a reason for hiding this comment

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

There's only a prompt for the first time the credential store is to be used for an app (only for some os's that are more stringent with permissions). If something with how the app is built (maybe bundle name? don't remember off the top of my head) changes then this also happens. When we update the electron version in compass-dev we see the prompts as the identifier changes then. As Alena mentioned it happened once around two years ago for VSCode which caused users to see this prompt again. I don't think we need to worry much about that, I don't see it as a bad thing to account for though.
As Anna said, the prompt is from the OS's credential store. Maybe the user could have configured it in a way where they get a prompt each time it's requested.

https://github.com/mongodb-js/compass/blob/main/packages/hadron-build/lib/target.js

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Understood and thank you for the explanation here @Anemy
In that case, we can still safely assume that keytar will be available to our extension, it is just that the calls to get secret would fail (if the user did not unlock or took too long to unlock), right?
If so then I would particularly account for that failure (originating from keytar.getPassword).

As of now, these errors would just bubble up and eventually get caught in _getConnectionInfoWithSecret and the connection will not be marked as migrated (ready to be re-attempted for migration).

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM

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 just addressed this in my commit here. The connections which will fail migration for anything other than keytar not available are not marked as migrated and hence can be re-attempted again in the next load.

There is a kinda bad side effect of this which is - such connections (for which our _migrateConnectionWithKeytarSecrets rejected) will not appear in our Connection tree when the extension loads. I was also able to confirm from the following code that this is an existing behaviour.

I have created this ticket to define and agree upon a behaviour of "loading of the connections" when we are not able to fetch the secrets (for whichever reason) for some of the connections. Please let me know if this is something we should absolutely fix within this PR.

…that no notification / tracking was sent if the failed migrations are not from current load of extension
@himanshusinghs himanshusinghs force-pushed the VSCODE-435-keytar-secrets-migration branch from 24bfe9e to 36e17c0 Compare July 5, 2023 12:21
export type SecretStorageLocationType =
| typeof SecretStorageLocation.Keytar
| typeof SecretStorageLocation.SecretStorage;

Copy link
Contributor

Choose a reason for hiding this comment

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

Could it be just export type SecretStorageLocationType = 'vscode.Keytar' | 'vscode.SecretStorage';? Do we need at all SecretStorageLocation? cc @addaleax

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea I think it could be. I probably did it this way just so I also have a string const to use in places where the types are not available from the context (for example tests and a couple other places in the code as well where we declare a connection info like object with secret storage).

Copy link
Member

@Anemy Anemy left a comment

Choose a reason for hiding this comment

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

Looking good, left a question on keytar handling. Also +1 on updating the text on the info message we show for those cases where things can't be loaded.

src/connectionController.ts Outdated Show resolved Hide resolved
src/connectionController.ts Show resolved Hide resolved
@alenakhineika
Copy link
Contributor

I feel like the PR is good to be merged when the text message we show to users in case of migration failure is updated 👍 great job! 👏

@himanshusinghs himanshusinghs force-pushed the VSCODE-435-keytar-secrets-migration branch from e65ab1b to 4388609 Compare July 5, 2023 17:15
@himanshusinghs himanshusinghs force-pushed the VSCODE-435-keytar-secrets-migration branch from 4388609 to 4250dd6 Compare July 5, 2023 17:31
@himanshusinghs himanshusinghs merged commit c2b23a1 into main Jul 6, 2023
@himanshusinghs himanshusinghs deleted the VSCODE-435-keytar-secrets-migration branch July 6, 2023 11:40
@himanshusinghs himanshusinghs linked an issue Jul 14, 2023 that may be closed by this pull request
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.

Keytar Migration Plan Bug Report: Connections not storing on linux with credential store
4 participants