Skip to content

Commit

Permalink
Enable AzKeyStore with keyring in Linux (#20296)
Browse files Browse the repository at this point in the history
* Enable AzKeystore with keyring in Linux
* Use the seperated file names for protected and unprotected AzKeystore
* Optimize the warning message for customers
* Update common library version.
* Upgraded reference of azure-powershell-common to 1.3.67-previewk
  • Loading branch information
msJinLei committed Dec 5, 2022
1 parent 98885f3 commit 8d960fb
Show file tree
Hide file tree
Showing 13 changed files with 71 additions and 26 deletions.
2 changes: 1 addition & 1 deletion src/Accounts/Accounts.Test/AutosaveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ private AzKeyStore SetMockedAzKeyStore()
storageMocker.Setup(f => f.Create()).Returns(storageMocker.Object);
storageMocker.Setup(f => f.ReadData()).Returns(new byte[0]);
storageMocker.Setup(f => f.WriteData(It.IsAny<byte[]>())).Callback((byte[] s) => {});
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "keystore.cache", false, false, storageMocker.Object);
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "azkeystore", false, false, storageMocker.Object);
AzKeyStore.RegisterJsonConverter(typeof(ServicePrincipalKey), typeof(ServicePrincipalKey).Name);
AzKeyStore.RegisterJsonConverter(typeof(SecureString), typeof(SecureString).Name, new SecureStringConverter());
return keyStore;
Expand Down
2 changes: 1 addition & 1 deletion src/Accounts/Accounts.Test/ProfileCmdletTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private AzKeyStore SetMockedAzKeyStore()
storageMocker.Setup(f => f.Create()).Returns(storageMocker.Object);
storageMocker.Setup(f => f.ReadData()).Returns(new byte[0]);
storageMocker.Setup(f => f.WriteData(It.IsAny<byte[]>())).Callback((byte[] s) => { });
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "keystore.cache", false, false, storageMocker.Object);
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "azkeystore", false, false, storageMocker.Object);
return keyStore;
}

Expand Down
14 changes: 8 additions & 6 deletions src/Accounts/Accounts/Account/ConnectAzureRmAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,10 @@ public override void ExecuteCmdlet()
if (CertificatePassword != null)
{
keyStore?.SaveKey(new ServicePrincipalKey(AzureAccount.Property.CertificatePassword, azureAccount.Id, Tenant), CertificatePassword);
if (GetContextModificationScope() == ContextModificationScope.CurrentUser && !keyStore.IsProtected)
{
WriteWarning(string.Format(Resources.ServicePrincipalWarning, keyStore.FileName, keyStore.Directory));
}
}
}

Expand All @@ -449,11 +453,9 @@ public override void ExecuteCmdlet()
{
keyStore?.SaveKey(new ServicePrincipalKey(AzureAccount.Property.ServicePrincipalSecret
,azureAccount.Id, Tenant), password);
if (GetContextModificationScope() == ContextModificationScope.CurrentUser)
if (GetContextModificationScope() == ContextModificationScope.CurrentUser && !keyStore.IsProtected)
{
var file = AzureSession.Instance.ARMProfileFile;
var directory = AzureSession.Instance.ARMProfileDirectory;
WriteWarning(string.Format(Resources.ServicePrincipalWarning, file, directory));
WriteWarning(string.Format(Resources.ServicePrincipalWarning, keyStore.FileName, keyStore.Directory));
}
}
if (azureAccount.Type == "ClientAssertion" && FederatedToken != null)
Expand Down Expand Up @@ -711,8 +713,8 @@ public void OnImport()
}

AzKeyStore keyStore = null;
//AzureSession.Instance.KeyStoreFile
keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "keystore.cache", false, autoSaveEnabled);
keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, AzureSession.Instance.KeyStoreFile, false, autoSaveEnabled);
AzureSession.Instance.KeyStoreFile = keyStore.FileName;
AzKeyStore.RegisterJsonConverter(typeof(ServicePrincipalKey), typeof(ServicePrincipalKey).Name);
AzKeyStore.RegisterJsonConverter(typeof(SecureString), typeof(SecureString).Name, new SecureStringConverter());
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore);
Expand Down
1 change: 1 addition & 0 deletions src/Accounts/Accounts/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
-->

## Upcoming Release
* Enabled AzKeyStore with keyring in Linux.

## Version 2.10.4
* Enabled caching tokens when logging in with a client assertion. This fixed the incorrectly short lifespan of tokens.
Expand Down
5 changes: 5 additions & 0 deletions src/Accounts/Accounts/Context/ClearAzureRmContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ void ClearContext(AzureRmProfile profile, RMProfileClient client)
profile.TrySetDefaultContext(defaultContext);
result = true;
}
if (!AzureSession.Instance.TryGetComponent(AzKeyStore.Name, out AzKeyStore keyStore))
{
keyStore = null;
}
keyStore.Clear();
}

AzureSession.Instance.RaiseContextClearedEvent();
Expand Down
4 changes: 2 additions & 2 deletions src/Accounts/Accounts/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/Accounts/Accounts/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@
<value>Unable to set profile because environment variable '${0}' is null.</value>
</data>
<data name="ServicePrincipalWarning" xml:space="preserve">
<value>The provided service principal secret will be included in the '{0}' file found in the user profile ( {1} ). Please ensure that this directory has appropriate protections.</value>
<value>The provided service principal secret or certifcate password will be included in the '{0}' file found in the user profile ( {1} ). Please ensure that this directory has appropriate protections.</value>
</data>
<data name="ClientAssertionWarning" xml:space="preserve">
<value>The provided client id and assertion will be included in the '{0}' file found in the user profile ( {1} ). Please ensure that this directory has appropriate protections.</value>
Expand Down
2 changes: 1 addition & 1 deletion src/Accounts/Authentication.Test/AzKeyStorageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class AzKeyStorageTest
private Mock<IStorage> storageMocker = null;
private List<byte> storageChecker = null;
private string dummpyPath = "/home/dummy/.Azure";
private string keyStoreFileName = "keystore.cache";
private string keyStoreFileName = "azkeystore";

public AzKeyStorageTest()
{
Expand Down
5 changes: 2 additions & 3 deletions src/Accounts/Authentication/AzureSessionInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ static ContextAutosaveSettings InitializeSessionSettings(IDataStore store, strin
ContextDirectory = profileDirectory,
Mode = ContextSaveMode.Process,
CacheFile = "msal.cache",
ContextFile = "AzureRmContext.json",
KeyStoreFile = "keystore.cache"
ContextFile = "AzureRmContext.json"
};

var settingsPath = Path.Combine(profileDirectory, settingsFile);
Expand All @@ -177,7 +176,6 @@ static ContextAutosaveSettings InitializeSessionSettings(IDataStore store, strin
result.ContextDirectory = migrated ? profileDirectory : settings.ContextDirectory ?? result.ContextDirectory;
result.Mode = settings.Mode;
result.ContextFile = settings.ContextFile ?? result.ContextFile;
result.KeyStoreFile = settings.KeyStoreFile ?? result.KeyStoreFile;
result.Settings = settings.Settings;
bool updateSettings = false;
if (!settings.Settings.ContainsKey("InstallationId"))
Expand Down Expand Up @@ -270,6 +268,7 @@ static IAzureSession CreateInstance(IDataStore dataStore = null, Action<string>
session.ARMProfileFile = autoSave.ContextFile;
session.TokenCacheDirectory = autoSave.CacheDirectory;
session.TokenCacheFile = autoSave.CacheFile;
session.KeyStoreFile = "azkeystore";
autoSave.Settings.TryGetValue("InstallationId", out string installationId);
session.ExtendedProperties.Add("InstallationId", installationId);
InitializeConfigs(session, profilePath, writeWarning);
Expand Down
6 changes: 0 additions & 6 deletions src/Accounts/Authentication/ContextAutosaveSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,6 @@ public class ContextAutosaveSettings : IExtensibleSettings
/// </summary>
public string CacheFile { get; set; }

/// <summary>
/// The name of the keystore file
/// </summary>
public string KeyStoreFile { get; set; }


/// <summary>
/// Extensible settings for autosave
/// </summary>
Expand Down
21 changes: 21 additions & 0 deletions src/Accounts/Authentication/KeyStore/AzKeyStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ public IStorage Storage
set => _storage = value;
}

public bool IsProtected
{
get => Storage.IsProtected;
}

public string FileName
{
get => Storage.FileName;
}

public string Directory
{
get => Storage.Directory;
}

public AzKeyStore()
{

Expand Down Expand Up @@ -152,6 +167,12 @@ public void ClearCache()
_credentials.Clear();
}

public void Clear()
{
ClearCache();
Storage.Clear();
}

public void Flush()
{
IList<KeyStoreElement> serializableKeyStore = new List<KeyStoreElement>();
Expand Down
8 changes: 8 additions & 0 deletions src/Accounts/Authentication/KeyStore/IStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,13 @@ public interface IStorage
void WriteData(byte[] data);

Exception GetLastError();

string FileName { get; set; }
string Directory { get; set; }

bool IsProtected
{
get;
}
}
}
25 changes: 20 additions & 5 deletions src/Accounts/Authentication/KeyStore/StorageWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.Azure.Commands.Common.Authentication.Properties;
using Microsoft.Identity.Client.Extensions.Msal;
using System;
using System.Collections.Generic;
using System.Threading;

namespace Microsoft.Azure.Commands.ResourceManager.Common
Expand All @@ -29,6 +30,13 @@ class StorageWrapper : IStorage

private Storage _storage = null;

private bool isProtected;
public bool IsProtected
{
get => isProtected;
private set => isProtected = value;
}

static ReaderWriterLockSlim storageLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

public StorageWrapper()
Expand All @@ -43,25 +51,32 @@ public IStorage Create()
{
throw new InvalidOperationException(Resources.StorageLockConflicts);
}
string tempFileName = null;
try
{
storageProperties = new StorageCreationPropertiesBuilder(FileName, Directory)
tempFileName = $"{FileName}.cache";
storageProperties = new StorageCreationPropertiesBuilder(tempFileName, Directory)
.WithMacKeyChain(KeyChainServiceName + ".other_secrets", FileName)
.WithLinuxUnprotectedFile();
.WithLinuxKeyring(FileName, "default", "AzKeyStoreCache",
new KeyValuePair<string, string>("AzureClientID", "Microsoft.Developer.Azure.PowerShell"),
new KeyValuePair<string, string>("Microsoft.Developer.Azure.PowerShell", "1.0.0.0"));
_storage = Storage.Create(storageProperties.Build());
VerifyPersistence();
isProtected = true;
}
catch (MsalCachePersistenceException e)
catch (Exception e)
{
_lastError = e;
_storage.Clear();
storageProperties = new StorageCreationPropertiesBuilder(FileName, Directory).WithUnprotectedFile();
tempFileName = $"{FileName}.json";
storageProperties = new StorageCreationPropertiesBuilder(tempFileName, Directory).WithUnprotectedFile();
_storage = Storage.Create(storageProperties.Build());
isProtected = false;
}
finally
{
storageLock.ExitWriteLock();
}
FileName = tempFileName ?? FileName;
return this;
}

Expand Down

0 comments on commit 8d960fb

Please sign in to comment.