Skip to content

Commit 75e99f8

Browse files
authored
Merge pull request #32 from Azure/key-store-refactor
Allow for SPN context persistence in .NET Core
2 parents 515d1a7 + 8a50440 commit 75e99f8

20 files changed

+343
-58
lines changed

src/Authentication.Abstractions/AzureAccount.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -124,17 +124,21 @@ public static class Property
124124
/// Login Uri for Managed Service Login
125125
/// </summary>
126126
MSILoginUri = "MSILoginUri",
127-
127+
128128
/// <summary>
129129
/// Backup login Uri for MSI
130130
/// </summary>
131131
MSILoginUriBackup = "MSILoginBackup",
132132

133-
134133
/// <summary>
135134
/// Secret that may be used with MSI login
136135
/// </summary>
137-
MSILoginSecret = "MSILoginSecret";
136+
MSILoginSecret = "MSILoginSecret",
137+
138+
/// <summary>
139+
/// Secret that may be used with service principal login
140+
/// </summary>
141+
ServicePrincipalSecret = "ServicePrincipalSecret";
138142
}
139143
}
140144
}

src/Authentication.ResourceManager/Authentication.ResourceManager.Netcore.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@
5656
</ItemGroup>
5757

5858
<ItemGroup>
59+
<Compile Remove="AzureRmServicePrincipalKeyStore.cs" />
60+
<Compile Remove="CredStore.cs" />
5961
<Compile Remove="Properties\AssemblyInfo.cs" />
62+
<Compile Remove="ServicePrincipalKeyStore.cs" />
6063
<EmbeddedResource Remove="Properties\AssemblyInfo.cs" />
6164
<None Remove="Properties\AssemblyInfo.cs" />
6265
<Content Remove="Properties\AssemblyInfo.cs" />

src/Authentication.ResourceManager/Authentication.ResourceManager.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<WarningLevel>4</WarningLevel>
2929
<RunCodeAnalysis>true</RunCodeAnalysis>
3030
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
31+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
3132
</PropertyGroup>
3233
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
3334
<DebugType>pdbonly</DebugType>
@@ -46,13 +47,15 @@
4647
<DelaySign>true</DelaySign>
4748
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
4849
<Prefer32Bit>false</Prefer32Bit>
50+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
4951
</PropertyGroup>
5052
<ItemGroup>
5153
<Compile Include="AzureRmAutosaveProfile.cs" />
5254
<Compile Include="AzureRmProfile.cs" />
5355
<Compile Include="AzureRmProfileConverter.cs" />
5456
<Compile Include="Common\PSObjectExtensions.cs" />
5557
<Compile Include="ContextModelExtensions.cs" />
58+
<Compile Include="CredStore.cs" />
5659
<Compile Include="IProfileOperations.cs" />
5760
<Compile Include="Models\AzureContextConverter.cs" />
5861
<Compile Include="Models\PSAzureContext.cs" />
@@ -76,6 +79,7 @@
7679
<Compile Include="Serialization\LegacyAzureRMProfile.cs" />
7780
<Compile Include="Serialization\LegacyAzureTenant.cs" />
7881
<Compile Include="Serialization\ModelConversionExtensions.cs" />
82+
<Compile Include="AzureRmServicePrincipalKeyStore.cs" />
7983
</ItemGroup>
8084
<ItemGroup>
8185
<EmbeddedResource Include="Properties\Resources.resx">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
using Microsoft.Azure.Commands.Common.Authentication;
15+
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
16+
using Microsoft.Azure.Commands.Common.Authentication.Models;
17+
using Microsoft.Azure.Commands.Common.Authentication.ResourceManager;
18+
using System;
19+
using System.Collections;
20+
using System.Collections.Generic;
21+
using System.IO;
22+
using System.Linq;
23+
using System.Security;
24+
25+
namespace Microsoft.Azure.Commands.ResourceManager.Common
26+
{
27+
/// <summary>
28+
/// Helper class to store service principal keys and retrieve them
29+
/// from the Windows Credential Store.
30+
/// </summary>
31+
public class AzureRmServicePrincipalKeyStore : IServicePrincipalKeyStore
32+
{
33+
public const string Name = "ServicePrincipalKeyStore";
34+
private IDictionary<string, SecureString> _credentials { get; set; }
35+
36+
public AzureRmServicePrincipalKeyStore() : this(null) { }
37+
38+
public AzureRmServicePrincipalKeyStore(IAzureContextContainer profile)
39+
{
40+
_credentials = new Dictionary<string, SecureString>();
41+
if (profile != null && profile.Accounts != null)
42+
{
43+
foreach (var account in profile.Accounts)
44+
{
45+
if (account != null && account.ExtendedProperties.ContainsKey(AzureAccount.Property.ServicePrincipalSecret))
46+
{
47+
var appId = account.Id;
48+
var tenantId = account.GetTenants().FirstOrDefault();
49+
var key = CreateKey(appId, tenantId);
50+
var servicePrincipalSecret = account.ExtendedProperties[AzureAccount.Property.ServicePrincipalSecret];
51+
_credentials[key] = ConvertToSecureString(servicePrincipalSecret);
52+
}
53+
}
54+
}
55+
}
56+
57+
public void SaveKey(string appId, string tenantId, SecureString serviceKey)
58+
{
59+
var key = CreateKey(appId, tenantId);
60+
_credentials[key] = serviceKey;
61+
}
62+
63+
public SecureString GetKey(string appId, string tenantId)
64+
{
65+
IntPtr pCredential = IntPtr.Zero;
66+
try
67+
{
68+
var key = CreateKey(appId, tenantId);
69+
return _credentials[key];
70+
71+
}
72+
catch
73+
{
74+
// we could be running in an environment that does not have credentials store
75+
}
76+
77+
return null;
78+
}
79+
80+
81+
public void DeleteKey(string appId, string tenantId)
82+
{
83+
try
84+
{
85+
var key = CreateKey(appId, tenantId);
86+
_credentials.Remove(key);
87+
}
88+
catch
89+
{
90+
}
91+
}
92+
93+
private string CreateKey(string appId, string tenantId)
94+
{
95+
return $"{appId}_{tenantId}";
96+
}
97+
98+
internal SecureString ConvertToSecureString(string password)
99+
{
100+
if (password == null)
101+
throw new ArgumentNullException("password");
102+
103+
var securePassword = new SecureString();
104+
105+
foreach (char c in password)
106+
securePassword.AppendChar(c);
107+
108+
securePassword.MakeReadOnly();
109+
return securePassword;
110+
}
111+
}
112+
}

src/Authentication/Authentication/ServicePrincipalKeyStore.cs renamed to src/Authentication.ResourceManager/AzureRmServicePrincipalKeyStore.cs

+7-5
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,25 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using Microsoft.Azure.Commands.Common.Authentication;
1516
using System;
1617
using System.Runtime.InteropServices;
1718
using System.Security;
1819
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
1920

20-
namespace Microsoft.Azure.Commands.Common.Authentication
21+
namespace Microsoft.Azure.Commands.ResourceManager.Common
2122
{
2223
/// <summary>
2324
/// Helper class to store service principal keys and retrieve them
2425
/// from the Windows Credential Store.
2526
/// </summary>
26-
public static class ServicePrincipalKeyStore
27+
public class AzureRmServicePrincipalKeyStore : IServicePrincipalKeyStore
2728
{
29+
public const string Name = "ServicePrincipalKeyStore";
2830
private const string keyStoreUserName = "PowerShellServicePrincipalKey";
2931
private const string targetNamePrefix = "AzureSession:target=";
3032

31-
public static void SaveKey(string appId, string tenantId, SecureString serviceKey)
33+
public void SaveKey(string appId, string tenantId, SecureString serviceKey)
3234
{
3335
var credential = new CredStore.NativeMethods.Credential
3436
{
@@ -68,7 +70,7 @@ public static void SaveKey(string appId, string tenantId, SecureString serviceKe
6870
}
6971
}
7072

71-
public static SecureString GetKey(string appId, string tenantId)
73+
public SecureString GetKey(string appId, string tenantId)
7274
{
7375
IntPtr pCredential = IntPtr.Zero;
7476
try
@@ -104,7 +106,7 @@ public static SecureString GetKey(string appId, string tenantId)
104106
}
105107

106108

107-
public static void DeleteKey(string appId, string tenantId)
109+
public void DeleteKey(string appId, string tenantId)
108110
{
109111
CredStore.NativeMethods.CredDelete(CreateKey(appId, tenantId), CredStore.CredentialType.Generic, 0);
110112
}

src/Authentication/Authentication/CredStore.cs renamed to src/Authentication.ResourceManager/CredStore.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
using System.Runtime.ConstrainedExecution;
1717
using System.Runtime.InteropServices;
1818

19-
namespace Microsoft.Azure.Commands.Common.Authentication
19+
namespace Microsoft.Azure.Commands.ResourceManager.Common
2020
{
2121
/// <summary>
2222
/// Class wrapping PInvoke signatures for Windows Credential store
@@ -81,15 +81,15 @@ public Credential(string userName, string key, string value)
8181
this.flags = 0;
8282
this.type = CredentialType.Generic;
8383

84-
// set the key in the targetName
84+
// set the key in the targetName
8585
this.targetName = key;
8686

8787
this.targetAlias = null;
8888
this.comment = null;
8989
this.lastWritten.dwHighDateTime = 0;
9090
this.lastWritten.dwLowDateTime = 0;
9191

92-
// set the value in credentialBlob.
92+
// set the value in credentialBlob.
9393
this.credentialBlob = Marshal.StringToHGlobalUni(value);
9494
this.credentialBlobSize = (uint)((value.Length + 1) * 2);
9595

src/Authentication.Test/Authentication.Test.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@
6363
<EmbeddedResource Include="Resources\ResourceLocator.cs" />
6464
</ItemGroup>
6565
<ItemGroup>
66+
<ProjectReference Include="..\Authentication.ResourceManager\Authentication.ResourceManager.csproj">
67+
<Project>{69c2eb6b-cd63-480a-89a0-c489706e9299}</Project>
68+
<Name>Authentication.ResourceManager</Name>
69+
</ProjectReference>
6670
<ProjectReference Include="..\ResourceManager\ResourceManager.csproj">
6771
<Project>{3819d8a7-c62c-4c47-8ddd-0332d9ce1252}</Project>
6872
<Name>ResourceManager</Name>

src/Authentication.Test/AuthenticationFactoryTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using Microsoft.WindowsAzure.Commands.Utilities.Common;
2727
using Xunit.Abstractions;
2828
using Microsoft.Rest.Azure;
29+
using Microsoft.Azure.Commands.ResourceManager.Common;
2930

3031
namespace Common.Authentication.Test
3132
{

src/Authentication.Test/AzureSessionTests.cs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
2424
using System.IO;
2525
using Newtonsoft.Json;
26+
using Microsoft.Azure.Commands.ResourceManager.Common;
2627

2728
namespace Common.Authentication.Test
2829
{

src/Authentication.Test/Cmdlets/ConnectAccount.cs

+7
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ public override void ExecuteCmdlet()
9494
Account.SetProperty(AzureAccount.Property.Tenants, new[] { TenantId });
9595
}
9696

97+
#if NETSTANDARD
98+
if (!string.IsNullOrEmpty(Password))
99+
{
100+
Account.SetProperty(AzureAccount.Property.ServicePrincipalSecret, Password);
101+
}
102+
#endif
103+
97104
if (AzureRmProfileProvider.Instance.Profile == null)
98105
{
99106
ResourceManagerProfileProvider.InitializeResourceManagerProfile(true);

src/Authentication.Test/LoginTests.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ public class LoginTests
5151
public LoginTests()
5252
{
5353
AzureSessionInitializer.InitializeAzureSession();
54-
ResourceManagerProfileProvider.InitializeResourceManagerProfile();
54+
ProtectedProfileProvider.InitializeResourceManagerProfile();
55+
IServicePrincipalKeyStore keyStore = new AzureRmServicePrincipalKeyStore(AzureRmProfileProvider.Instance.Profile);
56+
AzureSession.Instance.RegisterComponent(ServicePrincipalKeyStore.Name, () => keyStore);
5557

5658
ContextAutosaveSettings settings = null;
5759
AzureSession.Modify((session) => EnableAutosave(session, true, out settings));

src/Authentication/Authentication.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
<Compile Include="Authentication\AdalTokenProvider.cs" />
5959
<Compile Include="Authentication\CertificateApplicationCredentialProvider.cs" />
6060
<Compile Include="Authentication\ConsoleParentWindow.cs" />
61-
<Compile Include="Authentication\CredStore.cs" />
6261
<Compile Include="Authentication\IdentityTokenHelpers.cs" />
6362
<Compile Include="Authentication\ITokenProvider.cs" />
6463
<Compile Include="Authentication\KeyStoreApplicationCredentialProvider.cs" />
@@ -67,7 +66,6 @@
6766
<Compile Include="Authentication\ProtectedFileTokenCache.cs" />
6867
<Compile Include="Authentication\RawAccessToken.cs" />
6968
<Compile Include="Authentication\RenewingTokenCredential.cs" />
70-
<Compile Include="Authentication\ServicePrincipalKeyStore.cs" />
7169
<Compile Include="Authentication\ServicePrincipalTokenProvider.cs" />
7270
<Compile Include="Authentication\UserTokenProvider.cs" />
7371
<Compile Include="AzureSessionInitializer.cs" />
@@ -77,6 +75,8 @@
7775
<Compile Include="Factories\CancelRetryHandler.cs" />
7876
<Compile Include="Factories\ClientFactory.cs" />
7977
<Compile Include="HttpClientOperationsFactory.cs" />
78+
<Compile Include="KeyStore\IServicePrincipalKeyStore.cs" />
79+
<Compile Include="KeyStore\ServicePrincipalKeyStore.cs" />
8080
<Compile Include="Utilities\HttpClientWithRetry.cs" />
8181
<Compile Include="ICacheable.cs" />
8282
<Compile Include="IHttpOperations.cs" />

src/Authentication/Authentication/AdalTokenProvider.cs

+24-12
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public AdalTokenProvider(IWin32Window parentWindow)
4444
this.servicePrincipalTokenProvider = new ServicePrincipalTokenProvider();
4545
}
4646

47+
public AdalTokenProvider(Func<IServicePrincipalKeyStore> getKeyStore)
48+
{
49+
this.userTokenProvider = new UserTokenProvider(new ConsoleParentWindow());
50+
this.servicePrincipalTokenProvider = new ServicePrincipalTokenProvider(getKeyStore);
51+
}
52+
4753
public IAccessToken GetAccessToken(
4854
AdalConfiguration config,
4955
string promptBehavior,
@@ -76,15 +82,21 @@ public IAccessToken GetAccessTokenWithCertificate(
7682
default:
7783
throw new ArgumentException(string.Format(Resources.UnsupportedCredentialType, credentialType), "credentialType");
7884
}
79-
}
85+
}
8086
#else
8187
public AdalTokenProvider()
8288
{
8389
this.userTokenProvider = new UserTokenProvider();
8490
this.servicePrincipalTokenProvider = new ServicePrincipalTokenProvider();
8591
}
86-
87-
public IAccessToken GetAccessToken(
92+
93+
public AdalTokenProvider(Func<IServicePrincipalKeyStore> getKeyStore)
94+
{
95+
this.userTokenProvider = new UserTokenProvider();
96+
this.servicePrincipalTokenProvider = new ServicePrincipalTokenProvider(getKeyStore);
97+
}
98+
99+
public IAccessToken GetAccessToken(
88100
AdalConfiguration config,
89101
string promptBehavior,
90102
Action<string> promptAction,
@@ -96,19 +108,19 @@ public IAccessToken GetAccessToken(
96108
{
97109
case AzureAccount.AccountType.User:
98110
return userTokenProvider.GetAccessToken(
99-
config,
111+
config,
100112
promptBehavior,
101-
promptAction,
102-
userId,
103-
password,
113+
promptAction,
114+
userId,
115+
password,
104116
credentialType);
105117
case AzureAccount.AccountType.ServicePrincipal:
106118
return servicePrincipalTokenProvider.GetAccessToken(
107-
config,
108-
promptBehavior,
109-
promptAction,
110-
userId,
111-
password,
119+
config,
120+
promptBehavior,
121+
promptAction,
122+
userId,
123+
password,
112124
credentialType);
113125
default:
114126
throw new ArgumentException(Resources.UnsupportedCredentialType, "credentialType");

0 commit comments

Comments
 (0)