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

Revamp credential storage system to allow for smarter searching #162

Merged
merged 16 commits into from
Sep 17, 2020

Conversation

mjcheetham
Copy link
Collaborator

This PR should hopefully address several bugs which originate from the (flawed) design of the credential storage abstraction ( #142, #160) as well as a simple port number parsing bug (#156)

The original design/assumption was that for each request we get from Git (via a get call) we could create a unique 'credential key' that encoded the username and URL for the hosting provider. Whilst this worked for GitHub, Azure Repos, and Bitbucket when using one user per provider (or AzDevOps organisation), it failed to work for the generic provider when the username was not specified as part of the remote URL, nor did it work for multi-user scenarios with the other providers.

Following the design of the osxkeychain and wincred Git credential helpers, we now store the username in a separate field from the URL (called a 'service' to use the macOS Keychain terminology). This means when doing a credential lookup in response to a get call, we can search for a credential with the matching URL (service), and optionally a matching account if a username was passed, or any user if none was specified.

One problem however arises from this multi-user support which is specific to the Azure Repos provider..

Background: Azure Repos supports two kinds of URL: org.visualstudio.com and dev.azure.com/org. In the former we have all the information we need to perform authentication in the authority (protocol + hostname + orgname), but with the latter we require at least the first component of the path to get the organisation name.

Git only supplies the authority information (protocol + hostname) by default, meaning we have to force the useHttpPath Git configuration option to true meaning we always get the path component. This also meant that for Azure Repos users using the dev.azure.com-style URL we could not differentiate between a real desire to store credentials against the full remote URL including path or not. This was already an existing limitation.

However, to help 'workaround' this URL+Git design incompatibility, Azure Repos also made the decision to recommend users put the AzDevOps organisation name as the user info in the remote URL for dev.azure.com URLs. For example: https://org@dev.azure.com/org/project/_git/repo. Previously we ignored the username (since we only supported one user), but now we have a problem with storing credentials against the real username vs the 'fake' org username value.

Without a change to Git to either: a) always provide the full remote URL on each get request as a separate input line, or b) provide a way to maintain state between the get and store/erase calls, we cannot implement multi-user nor full-path support for dev.azure.com users. For now, we maintain the existing behaviour of one one user and no full path storage support for credentials against the dev.azure.com-style URLs.

We do however implement full mutli-user and full-path-storage support for the older *.visualstudio.com-style URLs.
For those who wish to use multiple users per Azure DevOps organisation, or store a credential against the full remote URL, they must use the vs.com-style URLs which are still supported.

@mjcheetham mjcheetham added bug A bug in Git Credential Manager enhancement New feature or request labels Sep 9, 2020
Copy link
Contributor

@derrickstolee derrickstolee left a comment

Choose a reason for hiding this comment

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

Finished my first pass. Just a few random thoughts here and there.

Question: What is the user experience for someone using a dev.azure.com URL with current GCM Core and then they upgrade? Do they now need to re-auth for every organization? Or will the existing org@dev.azure.com credentials still work? It is harder to be confident in such compatibility scenarios, as the tests only check the current version of the code.

@mjcheetham
Copy link
Collaborator Author

Finished my first pass. Just a few random thoughts here and there.

Question: What is the user experience for someone using a dev.azure.com URL with current GCM Core and then they upgrade? Do they now need to re-auth for every organization? Or will the existing org@dev.azure.com credentials still work? It is harder to be confident in such compatibility scenarios, as the tests only check the current version of the code.

Current GCM Core does the same thing.. for dev.azure.com/org URLs we only ever store against the org URL.
In fact, current GCM also does the same thing for org.vs.com URLs when it didn't need to, so this change is actually only increasing the number of scenarios, not cutting them.

Copy link
Contributor

@derrickstolee derrickstolee left a comment

Choose a reason for hiding this comment

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

I didn't find anything new in a second read.

@mjcheetham mjcheetham force-pushed the newcred2 branch 3 times, most recently from 245c05e to 62dff0a Compare September 17, 2020 14:57
Update the ICredentialStore and ICredential API to allow for smarter
searching of stored credentials.

The new API exposes filtering by "service name" and "account"
separately. Service name will typically be the URL the credential is
stored against, and the account will be the username associated with the
credential.
Update the macOS Keychain component implementation to match the new
ICredentialStore interface. We now use the `SecItemCopyMatching` to
perform a general query for items, and return a specialised credential
object including all relevant attributes.
Update the Windows Credential Manager component to implement the new
ICredentialStore interface, including credential enumeration and
matching by account/user as well as 'service name' (target).
Recover the correct service name from the target name.

Since the target name may contain a userinfo component (for example
https://alice_domain.com@example.com/path), and the only place we store
the service name is in the target name, we need to strip out any
userinfo component.

We do this by looking for the "://" and the first '@' character before
the first '/', which act as the start and end of the userinfo component.
Update the SecretServiceCollection credential store (backed by
libsecret) to match the new ICredentialStore interface and access
model.
Update the remote URI generation from the program InputArguments to
support port numbers, and special characters in usernames.
Update the HostProvider base class to use the new 'service name'
abstraction rather than the simple 'unique credential key' one.

With this model we can better issue credential storage queries where the
username may not be specified explicitly in a get request (often the
case as the username is not always included in the remote URL for many
services).
Update the generic host provider to support the new credential storage
model and HostProvider base class APIs.
Update the Basic authentication component to match the new ICredential
interface.
Update the BitBucket provider to support and implement the new
credential storage/recall model and APIs.
Update the GitHub provider to implement and follow the new credential
storage/recall/matching model and ICredentialStore APIs.
Update the Azure Repos provider to support the new credential storage
API/model, as well as support remote URLs with explicit port numbers.
Now that we support multiple user accounts for each host/service/remote,
we have hit an interesting issue with Azure Repos.

With the introduction of the dev.azure.com-style URLs for Azure Repos
there was an unfortunate hack or workaround invented to add the AzDevOps org name
to the userinfo part of the remote URL, for example: org@dev.azure.com/org/blah.

Since GCM for Windows (and older versions of GCM Core that initially followed
the same model) always uses the value "PersonalAccessToken" for the username field
when storing credentials it was free to ignore the user part of the input.

The problem now is that since we support multiple user accounts, and will perform
an exact match against the credential (with user) if a username is specified in
the remote URL, we never find the credential we now store (we now always store
with the actual, real users' UPN).

To workaround this workaround (yuck) we ignore the username IF AND ONLY IF the
host is dev.azure.com, and return the first matching dev.azure.com/org
credential. The upshot of this is that dev.azure.com-style URLs do NOT
support multiple users OR full paths, however vs.com-style URLs will.
Add support for customising the namespace/prefix used to store
credentials in the OS credential store.

By default we use "git:{service}". Users can use GCM_NAMESPACE or
credential.namespace to set this to something different.

These configuration options are the same as in GCM for Windows to help
with migration.
Update the GCM Core Host Provider spec document and architecture
document to reflect the changes made to the abstract HostProvider
class; replacing GetCredentialKey with GetServiceName.
Fix a bug in the WindowsEnvironment implementation of the
LocateExectuable method. On .NET Core the UseShellExecute property of
ProcessStartInfo defaults to false, whereas on .NET Framework (the
runtime that we target on Windows) defaults to true. You must set this
to false if you want to redirect standard streams (which we want to do).

The .NET Framework-targeting build on Windows was throwing an exception
here(!)
@mjcheetham mjcheetham merged commit c883429 into git-ecosystem:master Sep 17, 2020
@mjcheetham mjcheetham deleted the newcred2 branch September 17, 2020 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A bug in Git Credential Manager enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants