Skip to content

Commit e692a81

Browse files
Store Secrets to Encrypted Storage (#19929)
* Store secrets to encrypted storage * Uses unprotected files in Linux * Update Changelog.md * Fix the pipeline issue * Update src/Accounts/Authentication/KeyStore/AzKeyStore.cs Co-authored-by: Beisi Zhou <[email protected]> Co-authored-by: Beisi Zhou <[email protected]>
1 parent 49564bd commit e692a81

20 files changed

+733
-37
lines changed

src/Accounts/Accounts.Test/AutosaveTests.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,38 @@
2222
using Xunit.Abstractions;
2323
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
2424
using System;
25+
using System.Security;
2526
using Microsoft.Azure.Commands.Profile.Context;
2627
using Microsoft.Azure.Commands.ScenarioTest;
2728
using Microsoft.Azure.Commands.ResourceManager.Common;
2829
using Microsoft.Azure.Commands.TestFx.Mocks;
30+
using Moq;
2931

3032
namespace Microsoft.Azure.Commands.Profile.Test
3133
{
3234
public class AutosaveTests
3335
{
3436
private MemoryDataStore dataStore;
3537
private MockCommandRuntime commandRuntimeMock;
38+
private AzKeyStore keyStore;
3639
public AutosaveTests(ITestOutputHelper output)
3740
{
3841
XunitTracingInterceptor.AddToContext(new XunitTracingInterceptor(output));
3942
commandRuntimeMock = new MockCommandRuntime();
4043
dataStore = new MemoryDataStore();
41-
ResetState();
44+
keyStore = SetMockedAzKeyStore();
45+
}
46+
47+
private AzKeyStore SetMockedAzKeyStore()
48+
{
49+
var storageMocker = new Mock<IStorage>();
50+
storageMocker.Setup(f => f.Create()).Returns(storageMocker.Object);
51+
storageMocker.Setup(f => f.ReadData()).Returns(new byte[0]);
52+
storageMocker.Setup(f => f.WriteData(It.IsAny<byte[]>())).Callback((byte[] s) => {});
53+
var keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "keystore.cache", false, false, storageMocker.Object);
54+
AzKeyStore.RegisterJsonConverter(typeof(ServicePrincipalKey), typeof(ServicePrincipalKey).Name);
55+
AzKeyStore.RegisterJsonConverter(typeof(SecureString), typeof(SecureString).Name, new SecureStringConverter());
56+
return keyStore;
4257
}
4358

4459
void ResetState()
@@ -54,6 +69,7 @@ void ResetState()
5469
Environment.SetEnvironmentVariable("Azure_PS_Data_Collection", "false");
5570
PowerShellTokenCacheProvider tokenProvider = new InMemoryTokenCacheProvider();
5671
AzureSession.Instance.RegisterComponent(PowerShellTokenCacheProvider.PowerShellTokenCacheProviderKey, () => tokenProvider, true);
72+
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore, true);
5773
}
5874

5975
[Fact]

src/Accounts/Accounts.Test/ProfileCmdletTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
using Microsoft.Azure.Commands.Common.Authentication;
1616
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
1717
using Microsoft.Azure.Commands.Common.Authentication.Models;
18+
using Microsoft.Azure.Commands.ResourceManager.Common;
1819
using Microsoft.Azure.Commands.ScenarioTest;
1920
using Microsoft.Azure.Commands.TestFx.Mocks;
2021
using Microsoft.Azure.ServiceManagement.Common.Models;
2122
using Microsoft.WindowsAzure.Commands.Common.Test.Mocks;
2223
using Microsoft.WindowsAzure.Commands.ScenarioTest;
2324
using Microsoft.WindowsAzure.Commands.Test.Utilities.Common;
2425
using Microsoft.WindowsAzure.Commands.Utilities.Common;
26+
using Moq;
2527
using System;
2628
using System.Linq;
2729
using System.Management.Automation;
@@ -34,6 +36,7 @@ public class ProfileCmdletTests : RMTestBase
3436
{
3537
private MemoryDataStore dataStore;
3638
private MockCommandRuntime commandRuntimeMock;
39+
private AzKeyStore keyStore;
3740

3841
public ProfileCmdletTests(ITestOutputHelper output)
3942
{
@@ -43,12 +46,25 @@ public ProfileCmdletTests(ITestOutputHelper output)
4346
AzureSession.Instance.DataStore = dataStore;
4447
commandRuntimeMock = new MockCommandRuntime();
4548
AzureSession.Instance.AuthenticationFactory = new MockTokenAuthenticationFactory();
49+
keyStore = SetMockedAzKeyStore();
50+
}
51+
52+
private AzKeyStore SetMockedAzKeyStore()
53+
{
54+
var storageMocker = new Mock<IStorage>();
55+
storageMocker.Setup(f => f.Create()).Returns(storageMocker.Object);
56+
storageMocker.Setup(f => f.ReadData()).Returns(new byte[0]);
57+
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);
59+
return keyStore;
4660
}
4761

4862
[Fact]
4963
[Trait(Category.AcceptanceType, Category.CheckIn)]
5064
public void SelectAzureProfileInMemory()
5165
{
66+
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore, true);
67+
5268
var profile = new AzureRmProfile { DefaultContext = new AzureContext() };
5369
var env = new AzureEnvironment(AzureEnvironment.PublicEnvironments.Values.FirstOrDefault());
5470
env.Name = "foo";
@@ -71,6 +87,7 @@ public void SelectAzureProfileInMemory()
7187
[Trait(Category.AcceptanceType, Category.CheckIn)]
7288
public void SelectAzureProfileBadPath()
7389
{
90+
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore, true);
7491
#pragma warning disable CS0618 // Suppress obsolescence warning: cmdlet name is changing
7592
ImportAzureRMContextCommand cmdlt = new ImportAzureRMContextCommand();
7693
#pragma warning restore CS0618 // Suppress obsolescence warning: cmdlet name is changing
@@ -88,6 +105,7 @@ public void SelectAzureProfileBadPath()
88105
[Trait(Category.AcceptanceType, Category.CheckIn)]
89106
public void SelectAzureProfileFromDisk()
90107
{
108+
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore, true);
91109
var profile = new AzureRmProfile();
92110
profile.EnvironmentTable.Add("foo", new AzureEnvironment(new AzureEnvironment( AzureEnvironment.PublicEnvironments.Values.FirstOrDefault())));
93111
profile.EnvironmentTable["foo"].Name = "foo";
@@ -110,6 +128,7 @@ public void SelectAzureProfileFromDisk()
110128
[Trait(Category.AcceptanceType, Category.CheckIn)]
111129
public void SaveAzureProfileInMemory()
112130
{
131+
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore, true);
113132
var profile = new AzureRmProfile();
114133
profile.EnvironmentTable.Add("foo", new AzureEnvironment(AzureEnvironment.PublicEnvironments.Values.FirstOrDefault()));
115134
profile.EnvironmentTable["foo"].Name = "foo";
@@ -134,6 +153,7 @@ public void SaveAzureProfileInMemory()
134153
[Trait(Category.AcceptanceType, Category.CheckIn)]
135154
public void SaveAzureProfileNull()
136155
{
156+
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore, true);
137157
#pragma warning disable CS0618 // Suppress obsolescence warning: cmdlet name is changing
138158
SaveAzureRMContextCommand cmdlt = new SaveAzureRMContextCommand();
139159
#pragma warning restore CS0618 // Suppress obsolescence warning: cmdlet name is changing
@@ -150,6 +170,7 @@ public void SaveAzureProfileNull()
150170
[Trait(Category.AcceptanceType, Category.CheckIn)]
151171
public void SaveAzureProfileFromDefault()
152172
{
173+
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore, true);
153174
var profile = new AzureRmProfile();
154175
profile.EnvironmentTable.Add("foo", new AzureEnvironment(AzureEnvironment.PublicEnvironments.Values.FirstOrDefault()));
155176
profile.DefaultContext = new AzureContext(new AzureSubscription(), new AzureAccount(), profile.EnvironmentTable["foo"]);

src/Accounts/Accounts/Account/ConnectAzureRmAccount.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
using Microsoft.Azure.PowerShell.Common.Config;
4242
using Microsoft.Identity.Client;
4343
using Microsoft.WindowsAzure.Commands.Common;
44-
using Microsoft.WindowsAzure.Commands.Common.CustomAttributes;
4544
using Microsoft.WindowsAzure.Commands.Common.Utilities;
4645
using Microsoft.WindowsAzure.Commands.Utilities.Common;
4746
using Microsoft.Azure.PowerShell.Common.Share.Survey;
@@ -426,7 +425,6 @@ public override void ExecuteCmdlet()
426425
azureAccount.SetProperty(AzureAccount.Property.CertificatePath, resolvedPath);
427426
if (CertificatePassword != null)
428427
{
429-
azureAccount.SetProperty(AzureAccount.Property.CertificatePassword, CertificatePassword.ConvertToString());
430428
keyStore?.SaveKey(new ServicePrincipalKey(AzureAccount.Property.CertificatePassword, azureAccount.Id, Tenant), CertificatePassword);
431429
}
432430
}
@@ -449,7 +447,6 @@ public override void ExecuteCmdlet()
449447

450448
if (azureAccount.Type == AzureAccount.AccountType.ServicePrincipal && password != null)
451449
{
452-
azureAccount.SetProperty(AzureAccount.Property.ServicePrincipalSecret, password.ConvertToString());
453450
keyStore?.SaveKey(new ServicePrincipalKey(AzureAccount.Property.ServicePrincipalSecret
454451
,azureAccount.Id, Tenant), password);
455452
if (GetContextModificationScope() == ContextModificationScope.CurrentUser)
@@ -713,16 +710,23 @@ public void OnImport()
713710
WriteInitializationWarnings(Resources.FallbackContextSaveModeDueCacheCheckError.FormatInvariant(ex.Message));
714711
}
715712

716-
if(!InitializeProfileProvider(autoSaveEnabled))
713+
AzKeyStore keyStore = null;
714+
//AzureSession.Instance.KeyStoreFile
715+
keyStore = new AzKeyStore(AzureSession.Instance.ARMProfileDirectory, "keystore.cache", false, autoSaveEnabled);
716+
AzKeyStore.RegisterJsonConverter(typeof(ServicePrincipalKey), typeof(ServicePrincipalKey).Name);
717+
AzKeyStore.RegisterJsonConverter(typeof(SecureString), typeof(SecureString).Name, new SecureStringConverter());
718+
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore);
719+
720+
if (!InitializeProfileProvider(autoSaveEnabled))
717721
{
718722
AzureSession.Instance.ARMContextSaveMode = ContextSaveMode.Process;
719723
autoSaveEnabled = false;
720724
}
721725

722-
#pragma warning disable CS0618 // Type or member is obsolete
723-
var keyStore = new AzKeyStore(AzureRmProfileProvider.Instance.Profile);
724-
#pragma warning restore CS0618 // Type or member is obsolete
725-
AzureSession.Instance.RegisterComponent(AzKeyStore.Name, () => keyStore);
726+
if (!keyStore.LoadStorage())
727+
{
728+
WriteInitializationWarnings(Resources.KeyStoreLoadingError);
729+
}
726730

727731
IAuthenticatorBuilder builder = null;
728732
if (!AzureSession.Instance.TryGetComponent(AuthenticatorBuilder.AuthenticatorBuilderKey, out builder))

src/Accounts/Accounts/AutoSave/DisableAzureRmContextAutosave.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ void DisableAutosave(IAzureSession session, bool writeAutoSaveFile, out ContextA
9292
builder.Reset();
9393
}
9494

95+
if (AzureSession.Instance.TryGetComponent(AzKeyStore.Name, out AzKeyStore keystore))
96+
{
97+
keystore.DisableAutoSaving();
98+
}
99+
95100
if (writeAutoSaveFile)
96101
{
97102
FileUtilities.EnsureDirectoryExists(session.ProfileDirectory);

src/Accounts/Accounts/AutoSave/EnableAzureRmContextAutosave.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ void EnableAutosave(IAzureSession session, bool writeAutoSaveFile, out ContextAu
102102
AzureSession.Instance.RegisterComponent(PowerShellTokenCacheProvider.PowerShellTokenCacheProviderKey, () => newCacheProvider, true);
103103
}
104104

105+
if (AzureSession.Instance.TryGetComponent(AzKeyStore.Name, out AzKeyStore keystore))
106+
{
107+
keystore.Flush();
108+
keystore.DisableAutoSaving();
109+
}
110+
105111

106112
if (writeAutoSaveFile)
107113
{

src/Accounts/Accounts/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
## Upcoming Release
2222
* Enabled caching tokens when logging in with a service principal. This could reduce network traffic and improve performance.
2323
* Upgraded target framework of Microsoft.Identity.Client to net461 [#20189]
24+
* Stored `ServicePrincipalSecret` and `CertificatePassword` into `AzKeyStore`.
2425

2526
## Version 2.10.3
2627
* Updated `Get-AzSubscription` to retrieve subscription by Id rather than listed all the subscriptions from server if subscription Id is provided. [#19115]

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

Lines changed: 10 additions & 1 deletion
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,4 +571,7 @@
571571
<value>The command {0} is part of Azure PowerShell module "{1}" and it is not installed. Run "Install-Module {1}" to install it.</value>
572572
<comment>0: command being not found; 1: its module</comment>
573573
</data>
574+
<data name="KeyStoreLoadingError" xml:space="preserve">
575+
<value>KeyStore cannot be loaded from storage. Please check the keystore file integrity or system compablity. The functions relate to context autosaving may be affected.</value>
576+
</data>
574577
</root>

src/Accounts/Authentication.ResourceManager/AzureRmProfile.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
using Microsoft.Azure.Commands.Common.Authentication.ResourceManager.Properties;
2626
using Microsoft.Azure.Commands.ResourceManager.Common;
2727
using Microsoft.Azure.Commands.ResourceManager.Common.Serialization;
28+
using Microsoft.WindowsAzure.Commands.Common;
2829

2930
using Newtonsoft.Json;
3031

@@ -205,15 +206,39 @@ private void Initialize(AzureRmProfile profile)
205206
EnvironmentTable[environment.Key] = environment.Value;
206207
}
207208

209+
AzKeyStore keystore = null;
210+
AzureSession.Instance.TryGetComponent(AzKeyStore.Name, out keystore);
211+
208212
foreach (var context in profile.Contexts)
209213
{
210-
this.Contexts.Add(context.Key, context.Value);
214+
this.Contexts.Add(context.Key, MigrateSecretToKeyStore(context.Value, keystore));
211215
}
212216

213217
DefaultContextKey = profile.DefaultContextKey ?? (profile.Contexts.Any() ? null : "Default");
214218
}
215219
}
216220

221+
private IAzureContext MigrateSecretToKeyStore(IAzureContext context, AzKeyStore keystore)
222+
{
223+
if (keystore != null)
224+
{
225+
var account = context.Account;
226+
if (account.IsPropertySet(AzureAccount.Property.ServicePrincipalSecret))
227+
{
228+
keystore?.SaveKey(new ServicePrincipalKey(AzureAccount.Property.ServicePrincipalSecret, account.Id, account.GetTenants().First())
229+
, account.ExtendedProperties.GetProperty(AzureAccount.Property.ServicePrincipalSecret).ConvertToSecureString());
230+
account.ExtendedProperties.Remove(AzureAccount.Property.ServicePrincipalSecret);
231+
}
232+
if (account.IsPropertySet(AzureAccount.Property.CertificatePassword))
233+
{
234+
keystore?.SaveKey(new ServicePrincipalKey(AzureAccount.Property.CertificatePassword, account.Id, account.GetTenants().First())
235+
, account.ExtendedProperties.GetProperty(AzureAccount.Property.CertificatePassword).ConvertToSecureString());
236+
account.ExtendedProperties.Remove(AzureAccount.Property.CertificatePassword);
237+
}
238+
}
239+
return context;
240+
}
241+
217242
private void LoadImpl(string contents)
218243
{
219244
}
@@ -311,6 +336,10 @@ public void Save(IFileProvider provider, bool serializeCache = true)
311336
// so that previous data is overwritten
312337
provider.Stream.SetLength(provider.Stream.Position);
313338
}
339+
340+
AzKeyStore keystore = null;
341+
AzureSession.Instance.TryGetComponent(AzKeyStore.Name, out keystore);
342+
keystore?.Flush();
314343
}
315344
finally
316345
{

0 commit comments

Comments
 (0)