Skip to content

Commit d687600

Browse files
authored
[BugFix] Enable AzKeyStore with keyring in Linux (#20341)
* Enable AzKeyStore with keyring in Linux (#20296) * Enable AzKeystore with keyring in Linux * Optimize the warning message for customers * Update common library version. * Address review comments
1 parent d126c2e commit d687600

File tree

13 files changed

+51
-19
lines changed

13 files changed

+51
-19
lines changed

src/Accounts/Accounts.Test/AutosaveTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private AzKeyStore SetMockedAzKeyStore()
5151
storageMocker.Setup(f => f.Create()).Returns(storageMocker.Object);
5252
storageMocker.Setup(f => f.ReadData()).Returns(new byte[0]);
5353
storageMocker.Setup(f => f.WriteData(It.IsAny<byte[]>())).Callback((byte[] s) => {});
54-
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "keystore.cache", false, false, storageMocker.Object);
54+
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "azkeystore", false, false, storageMocker.Object);
5555
AzKeyStore.RegisterJsonConverter(typeof(ServicePrincipalKey), typeof(ServicePrincipalKey).Name);
5656
AzKeyStore.RegisterJsonConverter(typeof(SecureString), typeof(SecureString).Name, new SecureStringConverter());
5757
return keyStore;

src/Accounts/Accounts.Test/ProfileCmdletTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ private AzKeyStore SetMockedAzKeyStore()
5555
storageMocker.Setup(f => f.Create()).Returns(storageMocker.Object);
5656
storageMocker.Setup(f => f.ReadData()).Returns(new byte[0]);
5757
storageMocker.Setup(f => f.WriteData(It.IsAny<byte[]>())).Callback((byte[] s) => { });
58-
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "keystore.cache", false, false, storageMocker.Object);
58+
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "azkeystore", false, false, storageMocker.Object);
5959
return keyStore;
6060
}
6161

src/Accounts/Accounts/Account/ConnectAzureRmAccount.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,10 @@ public override void ExecuteCmdlet()
426426
if (CertificatePassword != null)
427427
{
428428
keyStore?.SaveKey(new ServicePrincipalKey(AzureAccount.Property.CertificatePassword, azureAccount.Id, Tenant), CertificatePassword);
429+
if (GetContextModificationScope() == ContextModificationScope.CurrentUser && !keyStore.IsProtected)
430+
{
431+
WriteWarning(string.Format(Resources.ServicePrincipalWarning, AzureSession.Instance.KeyStoreFile, AzureSession.Instance.ARMProfileDirectory));
432+
}
429433
}
430434
}
431435

@@ -449,11 +453,9 @@ public override void ExecuteCmdlet()
449453
{
450454
keyStore?.SaveKey(new ServicePrincipalKey(AzureAccount.Property.ServicePrincipalSecret
451455
,azureAccount.Id, Tenant), password);
452-
if (GetContextModificationScope() == ContextModificationScope.CurrentUser)
456+
if (GetContextModificationScope() == ContextModificationScope.CurrentUser && !keyStore.IsProtected)
453457
{
454-
var file = AzureSession.Instance.ARMProfileFile;
455-
var directory = AzureSession.Instance.ARMProfileDirectory;
456-
WriteWarning(string.Format(Resources.ServicePrincipalWarning, file, directory));
458+
WriteWarning(string.Format(Resources.ServicePrincipalWarning, AzureSession.Instance.KeyStoreFile, AzureSession.Instance.ARMProfileDirectory));
457459
}
458460
}
459461
if (azureAccount.Type == "ClientAssertion" && FederatedToken != null)
@@ -711,8 +713,7 @@ public void OnImport()
711713
}
712714

713715
AzKeyStore keyStore = null;
714-
//AzureSession.Instance.KeyStoreFile
715-
keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "keystore.cache", false, autoSaveEnabled);
716+
keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, AzureSession.Instance.KeyStoreFile, false, autoSaveEnabled);
716717
AzKeyStore.RegisterJsonConverter(typeof(ServicePrincipalKey), typeof(ServicePrincipalKey).Name);
717718
AzKeyStore.RegisterJsonConverter(typeof(SecureString), typeof(SecureString).Name, new SecureStringConverter());
718719
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore);

src/Accounts/Accounts/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
-->
2020

2121
## Upcoming Release
22+
* Enabled AzKeyStore with keyring in Linux.
2223

2324
## Version 2.10.4
2425
* Enabled caching tokens when logging in with a client assertion. This fixed the incorrectly short lifespan of tokens.

src/Accounts/Accounts/Context/ClearAzureRmContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ void ClearContext(AzureRmProfile profile, RMProfileClient client)
8383
profile.TrySetDefaultContext(defaultContext);
8484
result = true;
8585
}
86+
if (AzureSession.Instance.TryGetComponent(AzKeyStore.Name, out AzKeyStore keyStore))
87+
{
88+
keyStore?.Clear();
89+
}
90+
8691
}
8792

8893
AzureSession.Instance.RaiseContextClearedEvent();

src/Accounts/Accounts/Properties/Resources.Designer.cs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Accounts/Accounts/Properties/Resources.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@
424424
<value>Unable to set profile because environment variable '${0}' is null.</value>
425425
</data>
426426
<data name="ServicePrincipalWarning" xml:space="preserve">
427-
<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>
427+
<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>
428428
</data>
429429
<data name="ClientAssertionWarning" xml:space="preserve">
430430
<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>

src/Accounts/Authentication.Test/AzKeyStorageTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class AzKeyStorageTest
3030
private Mock<IStorage> storageMocker = null;
3131
private List<byte> storageChecker = null;
3232
private string dummpyPath = "/home/dummy/.Azure";
33-
private string keyStoreFileName = "keystore.cache";
33+
private string keyStoreFileName = "azkeystore";
3434

3535
public AzKeyStorageTest()
3636
{

src/Accounts/Authentication/AzureSessionInitializer.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,7 @@ static ContextAutosaveSettings InitializeSessionSettings(IDataStore store, strin
160160
ContextDirectory = profileDirectory,
161161
Mode = ContextSaveMode.Process,
162162
CacheFile = "msal.cache",
163-
ContextFile = "AzureRmContext.json",
164-
KeyStoreFile = "keystore.cache"
163+
ContextFile = "AzureRmContext.json"
165164
};
166165

167166
var settingsPath = Path.Combine(profileDirectory, settingsFile);
@@ -177,7 +176,6 @@ static ContextAutosaveSettings InitializeSessionSettings(IDataStore store, strin
177176
result.ContextDirectory = migrated ? profileDirectory : settings.ContextDirectory ?? result.ContextDirectory;
178177
result.Mode = settings.Mode;
179178
result.ContextFile = settings.ContextFile ?? result.ContextFile;
180-
result.KeyStoreFile = settings.KeyStoreFile ?? result.KeyStoreFile;
181179
result.Settings = settings.Settings;
182180
bool updateSettings = false;
183181
if (!settings.Settings.ContainsKey("InstallationId"))
@@ -270,6 +268,7 @@ static IAzureSession CreateInstance(IDataStore dataStore = null, Action<string>
270268
session.ARMProfileFile = autoSave.ContextFile;
271269
session.TokenCacheDirectory = autoSave.CacheDirectory;
272270
session.TokenCacheFile = autoSave.CacheFile;
271+
session.KeyStoreFile = "azkeystore.cache";
273272
autoSave.Settings.TryGetValue("InstallationId", out string installationId);
274273
session.ExtendedProperties.Add("InstallationId", installationId);
275274
InitializeConfigs(session, profilePath, writeWarning);

src/Accounts/Authentication/ContextAutosaveSettings.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ public class ContextAutosaveSettings : IExtensibleSettings
5656
/// </summary>
5757
public string KeyStoreFile { get; set; }
5858

59-
6059
/// <summary>
6160
/// Extensible settings for autosave
6261
/// </summary>

src/Accounts/Authentication/KeyStore/AzKeyStore.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ public IStorage Storage
7171
set => _storage = value;
7272
}
7373

74+
public bool IsProtected
75+
{
76+
get => Storage.IsProtected;
77+
}
78+
7479
public AzKeyStore()
7580
{
7681

@@ -152,6 +157,12 @@ public void ClearCache()
152157
_credentials.Clear();
153158
}
154159

160+
public void Clear()
161+
{
162+
ClearCache();
163+
Storage.Clear();
164+
}
165+
155166
public void Flush()
156167
{
157168
IList<KeyStoreElement> serializableKeyStore = new List<KeyStoreElement>();

src/Accounts/Authentication/KeyStore/IStorage.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,10 @@ public interface IStorage
2828
void WriteData(byte[] data);
2929

3030
Exception GetLastError();
31+
32+
bool IsProtected
33+
{
34+
get;
35+
}
3136
}
3237
}

src/Accounts/Authentication/KeyStore/StorageWrapper.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Microsoft.Azure.Commands.Common.Authentication.Properties;
1515
using Microsoft.Identity.Client.Extensions.Msal;
1616
using System;
17+
using System.Collections.Generic;
1718
using System.Threading;
1819

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

3031
private Storage _storage = null;
3132

33+
private bool _protected;
34+
public bool IsProtected
35+
{
36+
get => _protected;
37+
private set => _protected = value;
38+
}
39+
3240
static ReaderWriterLockSlim storageLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
3341

3442
public StorageWrapper()
@@ -47,16 +55,19 @@ public IStorage Create()
4755
{
4856
storageProperties = new StorageCreationPropertiesBuilder(FileName, Directory)
4957
.WithMacKeyChain(KeyChainServiceName + ".other_secrets", FileName)
50-
.WithLinuxUnprotectedFile();
58+
.WithLinuxKeyring(FileName, "default", "AzKeyStoreCache",
59+
new KeyValuePair<string, string>("AzureClientID", "Microsoft.Developer.Azure.PowerShell"),
60+
new KeyValuePair<string, string>("Microsoft.Developer.Azure.PowerShell", "1.0.0.0"));
5161
_storage = Storage.Create(storageProperties.Build());
5262
VerifyPersistence();
63+
_protected = true;
5364
}
54-
catch (MsalCachePersistenceException e)
65+
catch (Exception e)
5566
{
5667
_lastError = e;
57-
_storage.Clear();
5868
storageProperties = new StorageCreationPropertiesBuilder(FileName, Directory).WithUnprotectedFile();
5969
_storage = Storage.Create(storageProperties.Build());
70+
_protected = false;
6071
}
6172
finally
6273
{

0 commit comments

Comments
 (0)