microsoft-authentication-library-for-dotnet icon indicating copy to clipboard operation
microsoft-authentication-library-for-dotnet copied to clipboard

Support for encrypted token cache on Linux without GUI

Open gabe-microsoft opened this issue 3 years ago • 28 comments

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

gabe-microsoft avatar Nov 23 '21 00:11 gabe-microsoft

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

Cheers,

josejimenezluna avatar Jun 27 '22 14:06 josejimenezluna

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.

bgavrilMS avatar Jun 28 '22 13:06 bgavrilMS

I also have this problem. Is plaintext not insecure?

jakeaufderheide avatar Nov 09 '22 17:11 jakeaufderheide

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 avatar Nov 09 '22 18:11 bgavrilMS

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

bgavrilMS avatar Dec 21 '22 16:12 bgavrilMS

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?

AlexKeySmith avatar Feb 02 '24 12:02 AlexKeySmith

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.

sam-mfb avatar Mar 18 '24 15:03 sam-mfb

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.

bpkroth avatar Apr 03 '24 20:04 bpkroth

@bpkroth - in memory caching works fine.

bgavrilMS avatar Apr 04 '24 10:04 bgavrilMS

@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!

sam-mfb avatar Apr 04 '24 10:04 sam-mfb

@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 avatar Apr 04 '24 13:04 bgavrilMS

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.

sam-mfb avatar Apr 04 '24 13:04 sam-mfb

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 avatar Apr 04 '24 14:04 bgavrilMS

@localden for the scenario.

bgavrilMS avatar Apr 04 '24 14:04 bgavrilMS

@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.

sam-mfb avatar Apr 04 '24 14:04 sam-mfb

@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.

localden avatar Apr 04 '24 17:04 localden

@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.

sam-mfb avatar Apr 04 '24 17:04 sam-mfb

with in memory encryption ...

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

rayluo avatar Apr 04 '24 17:04 rayluo

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.

sam-mfb avatar Apr 04 '24 17:04 sam-mfb

@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 avatar Apr 04 '24 22:04 bpkroth

@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 avatar Apr 04 '24 22:04 bpkroth

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

The scenario features I would like are:

  • as a developer convenient workflow (e.g., not being prompted to reauthenticate all the time, even across multiple invocations of git),
  • as an operator/user, don't store credentials on disk (e.g., stash the token in memory while my login session is alive and fetch it via a socket that's appropriately restricted just like with ssh-agent did for many many years)

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.

bpkroth avatar Apr 04 '24 22:04 bpkroth