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

Support for encrypted token cache on Linux without GUI #3033

Open
gabe-microsoft opened this issue Nov 23, 2021 · 31 comments
Open

Support for encrypted token cache on Linux without GUI #3033

gabe-microsoft opened this issue Nov 23, 2021 · 31 comments

Comments

@gabe-microsoft
Copy link

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
Copy link

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

Cheers,

@bgavrilMS
Copy link
Member

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
Copy link

I also have this problem. Is plaintext not insecure?

@bgavrilMS
Copy link
Member

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
Copy link
Member

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
Copy link

AlexKeySmith commented Feb 2, 2024

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
Copy link

sam-mfb commented Mar 18, 2024

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
Copy link

bpkroth commented Apr 3, 2024

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
Copy link
Member

@bpkroth - in memory caching works fine.

@sam-mfb
Copy link

sam-mfb commented Apr 4, 2024

@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
Copy link
Member

@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
Copy link

sam-mfb commented Apr 4, 2024

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
Copy link
Member

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
Copy link
Member

@localden for the scenario.

@sam-mfb
Copy link

sam-mfb commented Apr 4, 2024

@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
Copy link
Contributor

localden commented Apr 4, 2024

@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
Copy link

sam-mfb commented Apr 4, 2024

@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
Copy link
Contributor

rayluo commented Apr 4, 2024

with in memory encryption ...

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

@sam-mfb
Copy link

sam-mfb commented Apr 4, 2024

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
Copy link

bpkroth commented Apr 4, 2024

@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
Copy link

bpkroth commented Apr 4, 2024

@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

@bpkroth
Copy link

bpkroth commented Apr 4, 2024

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.

@agg23
Copy link

agg23 commented Jul 9, 2024

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
Copy link

sam-mfb commented Jul 9, 2024

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
Copy link

agg23 commented Jul 10, 2024

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
Copy link

sam-mfb commented Jul 11, 2024

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

@Tarun047
Copy link

Tarun047 commented Sep 2, 2024

I am not sure why this issue is still open.
It seems that MSAL has added support for LinuxKeyRingAccessor.

For anyone looking to use GCM on linux in headless mode.
Here are the steps that worked for me:

  1. Make sure you have pass installed for your distro.
  2. Generate a password store for your email address that is used for Azure DevOps / Gitlab / Github by running pass init <your_email_address>
  3. Then generate a GPG key pair by running gpg --gen-key and add your Git providers email and username.
  4. Enable GCM to use gpg by running git config --global credential.credentialStore gpg (or omit --global as per need).
  5. Add export GPG_TTY=$(tty) to your terminal profile config (~/.bashrc or ~/.zshrc)
  6. Hopefully this should allow you to store the credentials on headless linux without in a secure way.

@bgavrilMS
Copy link
Member

bgavrilMS commented Sep 3, 2024

I am not sure why this issue is still open. It seems that MSAL has added support for LinuxKeyRingAccessor.

For anyone looking to use GCM on linux in headless mode. Here are the steps that worked for me:

  1. Make sure you have pass installed for your distro.
  2. Generate a password store for your email address that is used for Azure DevOps / Gitlab / Github by running pass init <your_email_address>
  3. Then generate a GPG key pair by running gpg --gen-key and add your Git providers email and username.
  4. Enable GCM to use gpg by running git config --global credential.credentialStore gpg (or omit --global as per need).
  5. Add export GPG_TTY=$(tty) to your terminal profile config (~/.bashrc or ~/.zshrc)
  6. Hopefully this should allow you to store the credentials on headless linux without in a secure way.

@Tarun047 - MSAL supports only KeyRing for encryption at rest. It's possible, but very difficult, to get KeyRing to run on a headless Linux.
Git Credential Manager supports both pass and KeyRing. pass is easy to install on a headless Linux.

Git Credential Manager uses MSAL for getting tokens for Azure Dev Ops, but it uses its own OAuth implementation for getting tokens for GitHub, GitLab etc.

@erwinkramer
Copy link

Just had this in a scenario where I'm on Windows, using Visual Studio Code Dev Containers to run a Linux container, and binding my /.azure folder with MSAL credentials generated by az login. Had to disable cache encryption on Windows to make it work: az config set core.encrypt_token_cache=false.

There is also no clear message in either Azure CLI nor Azure SDK hinting at encryption issues, that could be a nice first step maybe.

@rayluo
Copy link
Contributor

rayluo commented Nov 21, 2024

Just had this in a scenario where I'm on Windows, using Visual Studio Code Dev Containers to run a Linux container, and binding my /.azure folder with MSAL credentials generated by az login. Had to disable cache encryption on Windows to make it work: az config set core.encrypt_token_cache=false.

Binding a Windows folder to a Linux container (or vice versa) is reasonable, it is just unfortunate that the token cache encryption was not designed to work across platforms.

There is also no clear message in either Azure CLI nor Azure SDK hinting at encryption issues, that could be a nice first step maybe.

I suppose, a better error message from Azure CLI is the only mitigation here. @erwinkramer you should move your message to Azure CLI's github repo and tag @jiasli there.

@thomasaarholt
Copy link

Is it possible to use the new broker support (#5086) for linux to improve the auth experience in WSL? There's no release yet (and I'm not a C# developer so I have no idea how to compile from source), but would be excited to try this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests