AzureAD / microsoft-authentication-library-for-dotnet

Microsoft Authentication Library (MSAL) for .NET

Home Page:https://aka.ms/msal-net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support for encrypted token cache on Linux without GUI

gabe-microsoft opened this issue · 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

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

Cheers,

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.

I also have this problem. Is plaintext not insecure?

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.

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

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?

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.

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 - in memory caching works fine.

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

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.

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

@localden for the scenario.

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

with in memory encryption ...

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

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

@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: git-ecosystem/git-credential-manager#1568

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.

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.

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.

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?

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 :)