Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/azure-cli-core/azure/cli/core/auth/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def logout_all_users(self):
self._msal_app.remove_account(account)

# Also remove token cache file
for e in file_extensions.values():
for e in file_extensions:
_try_remove(self._token_cache_file + e)

def logout_service_principal(self, client_id):
Expand All @@ -229,7 +229,7 @@ def logout_all_service_principal(self):
# remove service principal secrets
# TODO: As MSAL provides no interface to get all service principals in its token cache, this method can't
# clear all service principals' access tokens from MSAL token cache.
for e in file_extensions.values():
for e in file_extensions:
_try_remove(self._secret_file + e)

def get_user(self, user=None):
Expand Down
58 changes: 43 additions & 15 deletions src/azure-cli-core/azure/cli/core/auth/persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,64 @@
logger = get_logger(__name__)

# Files extensions for encrypted and plaintext persistence
file_extensions = {True: '.bin', False: '.json'}
file_extension_encrypted = '.bin'
file_extension_plaintext = '.json'
file_extension_signal = '.sig'
file_extensions = [file_extension_encrypted, file_extension_plaintext, file_extension_signal]

KEYCHAIN_SERVICE_NAME = 'Microsoft Azure CLI'
LIBSECRET_SCHEMA_NAME = 'Microsoft Azure CLI'

def load_persisted_token_cache(location, encrypt):
persistence = build_persistence(location, encrypt)
persistence = build_persistence(location, encrypt, type="Token cache")
return PersistedTokenCache(persistence)


def load_secret_store(location, encrypt):
persistence = build_persistence(location, encrypt)
persistence = build_persistence(location, encrypt, type="Secret store")
return SecretStore(persistence)


def build_persistence(location, encrypt):
def build_persistence(location, encrypt, type=None):
"""Build a suitable persistence instance based your current OS"""
location += file_extensions[encrypt]
logger.debug("build_persistence: location=%r, encrypt=%r", location, encrypt)
logger.debug("build_persistence: location=%r, encrypt=%r, type=%r", location, encrypt, type)
if encrypt:
if sys.platform.startswith('win'):
return FilePersistenceWithDataProtection(location)
# For FilePersistenceWithDataProtection, location is where the credential is stored.
path = location + file_extension_encrypted
logger.debug("Initializing FilePersistenceWithDataProtection: location=%r", path)
return FilePersistenceWithDataProtection(path)
if sys.platform.startswith('darwin'):
return KeychainPersistence(location, "my_service_name", "my_account_name")
# For KeychainPersistence, location is only used as a signal for the credential's last modified time.
# The credential is stored in Keychain identified by (service_name, account_name) combination.
# msal-extensions automatically computes account_name from signal_location.
# https://github.com/AzureAD/microsoft-authentication-extensions-for-python/pull/103
path = location + file_extension_signal
logger.debug("Initializing KeychainPersistence: location=%r", path)
return KeychainPersistence(path, service_name=KEYCHAIN_SERVICE_NAME, account_name=type)
if sys.platform.startswith('linux'):
return LibsecretPersistence(
location,
schema_name="my_schema_name",
attributes={"my_attr1": "foo", "my_attr2": "bar"}
)
else:
return FilePersistence(location)
# For LibsecretPersistence, location is only used as a signal for the credential's last modified time.
# The credential is stored in libsecret identified by (schema_name, attributes) combination.
# Doesn't seem to be a reason to use attributes to further filter the credential.
path = location + file_extension_signal
logger.debug("Initializing LibsecretPersistence: location=%r", path)
try:
attributes = {"type": type} if type else {}
return LibsecretPersistence(
path,
schema_name=LIBSECRET_SCHEMA_NAME,
attributes=attributes
)
except Exception as e:
# Warn the user and continue with FilePersistence.
# LibsecretPersistence are known to be unavailable in some Linux environments.
logger.debug("Failed to initialize LibsecretPersistence: %s", e)
logger.warning("TBD: Encryption is unavailable. Falling back to plaintext persistence."
"Please follow https://aka.ms/azure-cli-credential-encryption to enable encryption.")
# Either encryption is opted out or the OS is not supported for encryption. Use FilePersistence.
path = location + file_extension_plaintext
logger.debug("Initializing FilePersistence: location=%r", path)
return FilePersistence(path)


class SecretStore:
Expand Down
5 changes: 3 additions & 2 deletions src/azure-cli-core/azure/cli/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1514,9 +1514,10 @@ def get_secret_store(cli_ctx, name):


def should_encrypt_token_cache(cli_ctx):
# Only enable encryption for Windows (for now).
fallback = sys.platform.startswith('win32')
# Encryption enabled by default
fallback = True

# TODO: Remove the config and always enable encryption
# EXPERIMENTAL: Use core.encrypt_token_cache=False to turn off token cache encryption.
# encrypt_token_cache affects both MSAL token cache and service principal entries.
encrypt = cli_ctx.config.getboolean('core', 'encrypt_token_cache', fallback=fallback)
Expand Down
Loading