AzureAD / microsoft-authentication-library-for-dotnet

Microsoft Authentication Library (MSAL) for .NET
https://aka.ms/msal-net
MIT License
1.37k stars 338 forks source link

Support for encrypted token cache on Linux without GUI #3033

Open gabe-microsoft opened 2 years ago

gabe-microsoft commented 2 years ago

Is your feature request related to a problem? Please describe. Currently, when running Linux without a GUI (e.g., Azure Linux VM) MSAL uses a plain-text token cache. My understanding is that MSAL supports libsecret/secret credential stores, but these don't work properly without a GUI.

Specifically, I'm using Git Credential Manager (GCM) on an Azure Linux VM to work with git repos stored in Azure DevOps (ADO). When using ADO, GCM uses MSAL to acquire and store AAD tokens. Since MSAL doesn't support an encrypted credential store, I get the following warning from GCM:

warning: cannot persist Microsoft authentication token cache securely!
warning: using plain-text fallback token cache

Describe the solution you'd like MSAL could use an encrypted credential store like GPG/pass, which is used by GCM

josejimenezluna commented 2 years ago

Hi! I'm having the same issue. I was wondering whether there was any updates on this?

Cheers,

bgavrilMS commented 2 years ago

Hi @josejimenezluna - for now, you call fallback to a plaintext file, see the sample project in this repo.

We do not plan to add support for pass, but we would accept a contribution.

jakeaufderheide commented 1 year ago

I also have this problem. Is plaintext not insecure?

bgavrilMS commented 1 year ago

I also have this problem. Is plaintext not insecure?

Yes, it is. You could use an encrypted drive though.

Generally speaking, a malicious app can steal a token even if your file is encrypted - e.g. by sniffing the traffic or by decrypting the file - if the malicious app runs under the same user. Only Mac has app-level protection. App1 cannot access App2's keychain without permission from the user or special config.

bgavrilMS commented 1 year ago

This is not a priority for MSAL at the moment, but we could review a contribution. Fix should go here: https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet

AlexKeySmith commented 6 months ago

I presume this is the same issue behind the warning "Cannot persist Microsoft authentication token cache securely" when using the nuget credential provider in a devcontainer (mcr.microsoft.com/devcontainers/dotnet:1-6.0-jammy to be specific)?

Or are the projects independent from one another?

sam-mfb commented 5 months ago

This is not a priority for MSAL at the moment, but we could review a contribution. Fix should go here: https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet

We do not have the expertise to work alone on a contribution here, but would be interested in supporting one (including potentially financially) if someone with expertise was interested. The ability to use GCM with AzureDevOps (and therefore MSAL) in a linux container with no GUI is significant for us. The gpg/pass solution proposed would work, but so would something like an in-memory ephemeral storage. We'd mostly like to avoid unencrypted storage at rest.

bpkroth commented 4 months ago

This is not a priority for MSAL at the moment, but we could review a contribution. Fix should go here: https://github.com/AzureAD/microsoft-authentication-extensions-for-dotnet

We do not have the expertise to work alone on a contribution here, but would be interested in supporting one (including potentially financially) if someone with expertise was interested. The ability to use GCM with AzureDevOps (and therefore MSAL) in a linux container with no GUI is significant for us. The gpg/pass solution proposed would work, but so would something like an in-memory ephemeral storage. We'd mostly like to avoid unencrypted storage at rest.

+1, please prioritize this work necessary for non-GUI clients be able to cache credentials locally (even if just in memory) without plaintext storage.

bgavrilMS commented 4 months ago

@bpkroth - in memory caching works fine.

sam-mfb commented 4 months ago

@bgavrilMS - could you elaborate on this? or how to set it up?

my experience is that even when gcm is set to use in-memory caching the security warning described above is presented and the bearer token is cached in plaintext at ~/.local/.IdentityService/msal.cache. I detailed this further in a comment on an issue on the gcm repo, and they confirmed that was their understanding as well.

Is there some other way to do purely in memory caching that I'm missing? Thank you!

bgavrilMS commented 4 months ago

@sam-mfb - just create a PublicClientApplication and use it as a singleton or set WithCacheOptions(SharedCache) - for a static memory cache.

Note that the experience for the end user, particularly one using GCM, is jarring to say the least - you will need to login interactively every time the process restarts (and for GCM, this is for EVERY git command).

sam-mfb commented 4 months ago

Note that the experience for the end user, particularly one using GCM, is jarring to say the least - you will need to login interactively every time the process restarts (and for GCM, this is for EVERY git command).

Yeah, i think that would probably be prohibitive. I was hoping there would be some way to store the bearer token in memory for as long as the machine/container is up (or for some predetermined time) the same way the gcm memory cache works.

FWIW (and for anyone who finds this thread), what we currently do is use VS Code devcontainers which handles using GCM credentials from the host in the container. That works great, but it means we are locked into using VS Code to run devcontainers even if we don't need VSCode. So, the ideal would still be if there was a way to do a persistent, secure oauth login in a container without a GUI.

bgavrilMS commented 4 months ago

The caching extension will at least set permissions on that plaintext file, similar to chmod 600, so it's not entirely unprotected.

https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/main/src/client/Microsoft.Identity.Client.Extensions.Msal/Accessors/FileWithPermissions.cs

bgavrilMS commented 4 months ago

@localden for the scenario.

sam-mfb commented 4 months ago

@bgavrilMS , agreed and chmod 600 is probably as secure as most people are doing for storing ssh keys. so, in many cases this is probably still a better option (because the token is shorter lived than an ssh key). but we have been trying to close the "unencrypted at rest" credential issue as best we can (including eliminating plaintext ssh keys), which is how this came up.

thanks for your thoughts and attention. much appreciated.

localden commented 4 months ago

@sam-mfb @bpkroth @josejimenezluna @gabe-microsoft if the permissions are proprely set on the file like @bgavrilMS suggested, does that mitigate your own risk model? I do agree that encryption-at-rest is a component of the defense-in-depth strategy and permissions don't necessarily protect against exfiltration.

sam-mfb commented 4 months ago

@localden, i would say that correctly set permissions is a mitigation, but still leaves room for improvement in our threat model.

to explain a little further, our fundamental concern is minimizing the opportunity for an attacker to exfiltrate and use a session token. the difference between the token existing only in memory vs in a permission-controlled, unencrypted file seems to me to boil down to the possible opportunities for exfiltration. i will use the example of a docker container, but i think it applies to a bare metal example as well.

with in memory storage, an attacker who gained access to the host machine as the user (e.g., through malware) would only be able to exfiltrate the token if they container was running. so as soon as the developer stops the container, the token is gone and there's no exfiltration opportunity.

by contrast, with unencrypted storage on disk the, under the same attack scenario, the attacker has access to the token even after the container is stopped, until its underlying file storage is deleted (and any copies that may have been made). in addition, if the storage used by the container is persisted on a network that might also degrade the local access requirement (i.e., the attacker might only need network access). and, finally, it's easier for a user to accidentally change permissions than to export their memory.

so, in short, i think the window for exfiltration and the opportunities for misconfiguration are greater with on-disk vs. in-memory.

this isn't to say i think on-disk is completely insecure or that their aren't other mitigations that could be applied (e.g., restricting life of tokens; restricting devices/locations where tokens can be used, etc). but on the whole, i think from the perspective of reducing exfiltration opportunities, in-memory is better than unencrypted on-disk, even with correct permissions.

rayluo commented 4 months ago

with in memory encryption ...

Nit: AFAIK, MSAL's in-memory token cache is not encrypted either.

sam-mfb commented 4 months ago

with in memory encryption ...

Nit: AFAIK, MSAL's in-memory token cache is not encrypted either.

my mistake. i meant to write "in memory storage." my points assume memory is unencrypted.

bpkroth commented 4 months ago

@sam-mfb - just create a PublicClientApplication and use it as a singleton or set WithCacheOptions(SharedCache) - for a static memory cache.

Note that the experience for the end user, particularly one using GCM, is jarring to say the least - you will need to login interactively every time the process restarts (and for GCM, this is for EVERY git command).

@bgavrilMS initially I was expecting the git credential cache (external process accessed over a socket) to manage this, like @sam-mfb was referring to, not the git process itself, since as you point out, that cache is basically useless as it needs to reauth every single time git is invoked, which could be automatically in the background via vscode, for instance.

bpkroth commented 4 months ago

@localden as I guess this is actually more a complaint with the git-credential-manager than this particular library, I've made a new feature request there: https://github.com/git-ecosystem/git-credential-manager/issues/1568

bpkroth commented 4 months ago

re the threat model, I do agree with everything @sam-mfb said above.

The scenario features I would like are:

That does mean that until the token timesout or is revoked that in theory an attacker could connect to that socket and grab the token from that process, but it's at least not sitting there on disk long term and can't be used on another machine. Presumably if they already have access to my account or root on that machine, there are bigger problems and this isn't meant to account for that issue.

Short of that, the pass/gpg(-agent) solution originally described in this post could also work.

agg23 commented 1 month ago

It wasn't clear to me from this issue whether storing the secret in plaintext can allow for proper caching. With default configuration where it logs:

warning: cannot persist Microsoft authentication token cache securely!
warning: using plain-text fallback token cache

I am seeing the token persist for only a day or so, though git commands within that window only require auth the first time. I would like to avoid reauthing every time, especially since that requires password entry and 2FA code each time.

sam-mfb commented 1 month ago

I'm assuming you are using git-credential-manager.

If you are OK with the fact that msal library will store the token in your local filesystem, specifically to the location ~/.local/.IdentityService/msal.cache with permissions 600, then using git-credential-manager with this warning is fine.

If you are not OK with that, then you have to use git-credential-manager on a system that can use msal with a secure cache (i.e., a system with a gui).

But, yes, plaintext caching does work--the issue is just whether it fits with your threat model. BTW, my guess is that your 24 hour refresh time is the life of the token that Azure is issuing you rather than the life of the cache, but i'm not positive.

agg23 commented 1 month ago

Thank you for the information.


For the 24 hour refresh time, I'm not really sure what's going on. I do indeed have a token cache with a single token at ~/.local/.IdentityService/msal.cache. When I look at ADO, I see the following tokens:

image (I have sanitized the token names). I believe "Linux" is a manually created token, and all of the "Git"s are generated via this MSAL library. Notice that the expiration dates are a week out (which is the limit on our organization in ADO).

Is it possible that something would cause a token to be ignored on disk?

sam-mfb commented 1 month ago

To be honest, I'm not sure. But, btw, if you are using a PAT, you don't have to use this library -- a PAT can just be passed directly as a password to git. You only need to use MSAL if you are trying to do an Oauth2 flow or something similar in order to get a token. For example, if you wanted to log on with MFA, you would use MSAL to do the MFA login, that would give you an oauth2 token, and you'd pass that to Git. If you are already using a PAT, you can just pass that directly.

In other words, f you are using a PAT to get an oauth2 token you are taken an uncessary step. (And it's possible that what's happening is that, even though the PAT has a week long length -- the oauth2 token is smaller). This is probably getting us a little far afield from the is issue though :)