Skip to content

Commit

Permalink
Merge pull request #32 from Azure/key-store-refactor
Browse files Browse the repository at this point in the history
Allow for SPN context persistence in .NET Core
  • Loading branch information
markcowl authored Sep 21, 2018
2 parents 515d1a7 + 8a50440 commit 75e99f8
Show file tree
Hide file tree
Showing 20 changed files with 343 additions and 58 deletions.
10 changes: 7 additions & 3 deletions src/Authentication.Abstractions/AzureAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,21 @@ public static class Property
/// Login Uri for Managed Service Login
/// </summary>
MSILoginUri = "MSILoginUri",

/// <summary>
/// Backup login Uri for MSI
/// </summary>
MSILoginUriBackup = "MSILoginBackup",


/// <summary>
/// Secret that may be used with MSI login
/// </summary>
MSILoginSecret = "MSILoginSecret";
MSILoginSecret = "MSILoginSecret",

/// <summary>
/// Secret that may be used with service principal login
/// </summary>
ServicePrincipalSecret = "ServicePrincipalSecret";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@
</ItemGroup>

<ItemGroup>
<Compile Remove="AzureRmServicePrincipalKeyStore.cs" />
<Compile Remove="CredStore.cs" />
<Compile Remove="Properties\AssemblyInfo.cs" />
<Compile Remove="ServicePrincipalKeyStore.cs" />
<EmbeddedResource Remove="Properties\AssemblyInfo.cs" />
<None Remove="Properties\AssemblyInfo.cs" />
<Content Remove="Properties\AssemblyInfo.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<WarningLevel>4</WarningLevel>
<RunCodeAnalysis>true</RunCodeAnalysis>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
Expand All @@ -46,13 +47,15 @@
<DelaySign>true</DelaySign>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Prefer32Bit>false</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="AzureRmAutosaveProfile.cs" />
<Compile Include="AzureRmProfile.cs" />
<Compile Include="AzureRmProfileConverter.cs" />
<Compile Include="Common\PSObjectExtensions.cs" />
<Compile Include="ContextModelExtensions.cs" />
<Compile Include="CredStore.cs" />
<Compile Include="IProfileOperations.cs" />
<Compile Include="Models\AzureContextConverter.cs" />
<Compile Include="Models\PSAzureContext.cs" />
Expand All @@ -76,6 +79,7 @@
<Compile Include="Serialization\LegacyAzureRMProfile.cs" />
<Compile Include="Serialization\LegacyAzureTenant.cs" />
<Compile Include="Serialization\ModelConversionExtensions.cs" />
<Compile Include="AzureRmServicePrincipalKeyStore.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// ----------------------------------------------------------------------------------
//
// Copyright Microsoft Corporation
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------------------------------------------------------------
using Microsoft.Azure.Commands.Common.Authentication;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.Common.Authentication.Models;
using Microsoft.Azure.Commands.Common.Authentication.ResourceManager;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;

namespace Microsoft.Azure.Commands.ResourceManager.Common
{
/// <summary>
/// Helper class to store service principal keys and retrieve them
/// from the Windows Credential Store.
/// </summary>
public class AzureRmServicePrincipalKeyStore : IServicePrincipalKeyStore
{
public const string Name = "ServicePrincipalKeyStore";
private IDictionary<string, SecureString> _credentials { get; set; }

public AzureRmServicePrincipalKeyStore() : this(null) { }

public AzureRmServicePrincipalKeyStore(IAzureContextContainer profile)
{
_credentials = new Dictionary<string, SecureString>();
if (profile != null && profile.Accounts != null)
{
foreach (var account in profile.Accounts)
{
if (account != null && account.ExtendedProperties.ContainsKey(AzureAccount.Property.ServicePrincipalSecret))
{
var appId = account.Id;
var tenantId = account.GetTenants().FirstOrDefault();
var key = CreateKey(appId, tenantId);
var servicePrincipalSecret = account.ExtendedProperties[AzureAccount.Property.ServicePrincipalSecret];
_credentials[key] = ConvertToSecureString(servicePrincipalSecret);
}
}
}
}

public void SaveKey(string appId, string tenantId, SecureString serviceKey)
{
var key = CreateKey(appId, tenantId);
_credentials[key] = serviceKey;
}

public SecureString GetKey(string appId, string tenantId)
{
IntPtr pCredential = IntPtr.Zero;
try
{
var key = CreateKey(appId, tenantId);
return _credentials[key];

}
catch
{
// we could be running in an environment that does not have credentials store
}

return null;
}


public void DeleteKey(string appId, string tenantId)
{
try
{
var key = CreateKey(appId, tenantId);
_credentials.Remove(key);
}
catch
{
}
}

private string CreateKey(string appId, string tenantId)
{
return $"{appId}_{tenantId}";
}

internal SecureString ConvertToSecureString(string password)
{
if (password == null)
throw new ArgumentNullException("password");

var securePassword = new SecureString();

foreach (char c in password)
securePassword.AppendChar(c);

securePassword.MakeReadOnly();
return securePassword;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@
// limitations under the License.
// ----------------------------------------------------------------------------------

using Microsoft.Azure.Commands.Common.Authentication;
using System;
using System.Runtime.InteropServices;
using System.Security;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

namespace Microsoft.Azure.Commands.Common.Authentication
namespace Microsoft.Azure.Commands.ResourceManager.Common
{
/// <summary>
/// Helper class to store service principal keys and retrieve them
/// from the Windows Credential Store.
/// </summary>
public static class ServicePrincipalKeyStore
public class AzureRmServicePrincipalKeyStore : IServicePrincipalKeyStore
{
public const string Name = "ServicePrincipalKeyStore";
private const string keyStoreUserName = "PowerShellServicePrincipalKey";
private const string targetNamePrefix = "AzureSession:target=";

public static void SaveKey(string appId, string tenantId, SecureString serviceKey)
public void SaveKey(string appId, string tenantId, SecureString serviceKey)
{
var credential = new CredStore.NativeMethods.Credential
{
Expand Down Expand Up @@ -68,7 +70,7 @@ public static void SaveKey(string appId, string tenantId, SecureString serviceKe
}
}

public static SecureString GetKey(string appId, string tenantId)
public SecureString GetKey(string appId, string tenantId)
{
IntPtr pCredential = IntPtr.Zero;
try
Expand Down Expand Up @@ -104,7 +106,7 @@ public static SecureString GetKey(string appId, string tenantId)
}


public static void DeleteKey(string appId, string tenantId)
public void DeleteKey(string appId, string tenantId)
{
CredStore.NativeMethods.CredDelete(CreateKey(appId, tenantId), CredStore.CredentialType.Generic, 0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

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

// set the key in the targetName
// set the key in the targetName
this.targetName = key;

this.targetAlias = null;
this.comment = null;
this.lastWritten.dwHighDateTime = 0;
this.lastWritten.dwLowDateTime = 0;

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

Expand Down
4 changes: 4 additions & 0 deletions src/Authentication.Test/Authentication.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
<EmbeddedResource Include="Resources\ResourceLocator.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Authentication.ResourceManager\Authentication.ResourceManager.csproj">
<Project>{69c2eb6b-cd63-480a-89a0-c489706e9299}</Project>
<Name>Authentication.ResourceManager</Name>
</ProjectReference>
<ProjectReference Include="..\ResourceManager\ResourceManager.csproj">
<Project>{3819d8a7-c62c-4c47-8ddd-0332d9ce1252}</Project>
<Name>ResourceManager</Name>
Expand Down
1 change: 1 addition & 0 deletions src/Authentication.Test/AuthenticationFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using Xunit.Abstractions;
using Microsoft.Rest.Azure;
using Microsoft.Azure.Commands.ResourceManager.Common;

namespace Common.Authentication.Test
{
Expand Down
1 change: 1 addition & 0 deletions src/Authentication.Test/AzureSessionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using System.IO;
using Newtonsoft.Json;
using Microsoft.Azure.Commands.ResourceManager.Common;

namespace Common.Authentication.Test
{
Expand Down
7 changes: 7 additions & 0 deletions src/Authentication.Test/Cmdlets/ConnectAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ public override void ExecuteCmdlet()
Account.SetProperty(AzureAccount.Property.Tenants, new[] { TenantId });
}

#if NETSTANDARD
if (!string.IsNullOrEmpty(Password))
{
Account.SetProperty(AzureAccount.Property.ServicePrincipalSecret, Password);
}
#endif

if (AzureRmProfileProvider.Instance.Profile == null)
{
ResourceManagerProfileProvider.InitializeResourceManagerProfile(true);
Expand Down
4 changes: 3 additions & 1 deletion src/Authentication.Test/LoginTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ public class LoginTests
public LoginTests()
{
AzureSessionInitializer.InitializeAzureSession();
ResourceManagerProfileProvider.InitializeResourceManagerProfile();
ProtectedProfileProvider.InitializeResourceManagerProfile();
IServicePrincipalKeyStore keyStore = new AzureRmServicePrincipalKeyStore(AzureRmProfileProvider.Instance.Profile);
AzureSession.Instance.RegisterComponent(ServicePrincipalKeyStore.Name, () => keyStore);

ContextAutosaveSettings settings = null;
AzureSession.Modify((session) => EnableAutosave(session, true, out settings));
Expand Down
4 changes: 2 additions & 2 deletions src/Authentication/Authentication.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
<Compile Include="Authentication\AdalTokenProvider.cs" />
<Compile Include="Authentication\CertificateApplicationCredentialProvider.cs" />
<Compile Include="Authentication\ConsoleParentWindow.cs" />
<Compile Include="Authentication\CredStore.cs" />
<Compile Include="Authentication\IdentityTokenHelpers.cs" />
<Compile Include="Authentication\ITokenProvider.cs" />
<Compile Include="Authentication\KeyStoreApplicationCredentialProvider.cs" />
Expand All @@ -67,7 +66,6 @@
<Compile Include="Authentication\ProtectedFileTokenCache.cs" />
<Compile Include="Authentication\RawAccessToken.cs" />
<Compile Include="Authentication\RenewingTokenCredential.cs" />
<Compile Include="Authentication\ServicePrincipalKeyStore.cs" />
<Compile Include="Authentication\ServicePrincipalTokenProvider.cs" />
<Compile Include="Authentication\UserTokenProvider.cs" />
<Compile Include="AzureSessionInitializer.cs" />
Expand All @@ -77,6 +75,8 @@
<Compile Include="Factories\CancelRetryHandler.cs" />
<Compile Include="Factories\ClientFactory.cs" />
<Compile Include="HttpClientOperationsFactory.cs" />
<Compile Include="KeyStore\IServicePrincipalKeyStore.cs" />
<Compile Include="KeyStore\ServicePrincipalKeyStore.cs" />
<Compile Include="Utilities\HttpClientWithRetry.cs" />
<Compile Include="ICacheable.cs" />
<Compile Include="IHttpOperations.cs" />
Expand Down
36 changes: 24 additions & 12 deletions src/Authentication/Authentication/AdalTokenProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public AdalTokenProvider(IWin32Window parentWindow)
this.servicePrincipalTokenProvider = new ServicePrincipalTokenProvider();
}

public AdalTokenProvider(Func<IServicePrincipalKeyStore> getKeyStore)
{
this.userTokenProvider = new UserTokenProvider(new ConsoleParentWindow());
this.servicePrincipalTokenProvider = new ServicePrincipalTokenProvider(getKeyStore);
}

public IAccessToken GetAccessToken(
AdalConfiguration config,
string promptBehavior,
Expand Down Expand Up @@ -76,15 +82,21 @@ public IAccessToken GetAccessTokenWithCertificate(
default:
throw new ArgumentException(string.Format(Resources.UnsupportedCredentialType, credentialType), "credentialType");
}
}
}
#else
public AdalTokenProvider()
{
this.userTokenProvider = new UserTokenProvider();
this.servicePrincipalTokenProvider = new ServicePrincipalTokenProvider();
}

public IAccessToken GetAccessToken(

public AdalTokenProvider(Func<IServicePrincipalKeyStore> getKeyStore)
{
this.userTokenProvider = new UserTokenProvider();
this.servicePrincipalTokenProvider = new ServicePrincipalTokenProvider(getKeyStore);
}

public IAccessToken GetAccessToken(
AdalConfiguration config,
string promptBehavior,
Action<string> promptAction,
Expand All @@ -96,19 +108,19 @@ public IAccessToken GetAccessToken(
{
case AzureAccount.AccountType.User:
return userTokenProvider.GetAccessToken(
config,
config,
promptBehavior,
promptAction,
userId,
password,
promptAction,
userId,
password,
credentialType);
case AzureAccount.AccountType.ServicePrincipal:
return servicePrincipalTokenProvider.GetAccessToken(
config,
promptBehavior,
promptAction,
userId,
password,
config,
promptBehavior,
promptAction,
userId,
password,
credentialType);
default:
throw new ArgumentException(Resources.UnsupportedCredentialType, "credentialType");
Expand Down
Loading

0 comments on commit 75e99f8

Please sign in to comment.