Jacob Nosal
Jacob Nosal

Reputation: 11

Authenticating with Azure SDK for .Net ResourceManagementClient

I have an Azure Functions application that needs to retrieve a secret to authenticate with the Azure SDK for .Net.

[FunctionName("FunctionName")]
    public static async Task<HttpResponseMessage> Run(
        [HttpTrigger(AuthorizationLevel.Function, "POST")] HttpRequestMessage req,
        TraceWriter log
        )
    {
        string vaultName, secretName, clientId, clientSecret = string.Empty;
        IEnumerable<object> items = null;
        try
        {
            var context = await req.Content.ReadAsAsync<Context>();
            clientId = ConfigurationManager.AppSettings["clientId"].ToString();
            vaultName = ConfigurationManager.AppSettings["vaultName"].ToString();
            secretName = ConfigurationManager.AppSettings["secretName"].ToString();
            AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider();
            try
            {
                var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
                var secret = await keyVaultClient.GetSecretAsync(string.Format("https://{0}.vault.azure.net/secrets/{1}", vaultName, secretName));
                clientSecret = secret.Value;
            }
            catch
            {
                throw new Exception("Can't get secret.");
            }

            ServiceClientCredentials serviceCredentials = await ApplicationTokenProvider.LoginSilentAsync(context.cloudTenantId, context.cloudSubscriptionId, clientSecret);
            using (ResourceManagementClient client = new ResourceManagementClient(serviceCredentials))
            {
                items = OtherFunction(client);
            }
        }
        catch (Exception ex)
        {
            log.Error(ex.Message, ex);
            return req.CreateResponse(HttpStatusCode.InternalServerError, ex);
        }

        return req.CreateResponse(HttpStatusCode.OK, items);
    }

When this function executes, it returns a System.IO.FileNotFoundException with the following stacktrace

   at Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.<LoginSilentAsync>d__12.MoveNext()
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
   at Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.LoginSilentAsync(String domain, String clientId, String secret)
   at HcfApi.Src.PRIP1.<DesiredStateConfigCheck>d__0.MoveNext()

and message

Could not load file or assembly 'Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.28.3.860, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.

I suspect that this is a collision between versions of Microsoft.IdentityModel.Clients.ActiveDirectory used in ApplicationTokenProvider (>=2.28.3) and AzureServiceTokenProvider (>= 3.14.2).

Is there a way to get around these dependencies?

Update: I have a more detailed output of the exception encountered:

{
"ClassName": "System.IO.FileNotFoundException",
"Message": "Could not load file or assembly 'Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.28.3.860, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.",
"Data": null,
"InnerException": null,
"HelpURL": null,
"StackTraceString": "   at Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.<LoginSilentAsync>d__12.MoveNext()\r\n   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)\r\n   at Microsoft.Rest.Azure.Authentication.ApplicationTokenProvider.LoginSilentAsync(String domain, String clientId, String secret)\r\n   at HcfApi.Src.PRDS1.<StorageEncryptionCheck>d__1.MoveNext()",
"RemoteStackTraceString": null,
"RemoteStackIndex": 0,
"ExceptionMethod": "8\nMoveNext\nMicrosoft.Rest.ClientRuntime.Azure.Authentication, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35\nMicrosoft.Rest.Azure.Authentication.ApplicationTokenProvider+<LoginSilentAsync>d__12\nVoid MoveNext()",
"HResult": -2147024894,
"Source": "Microsoft.Rest.ClientRuntime.Azure.Authentication",
"WatsonBuckets": null,
"FileNotFound_FileName": "Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.28.3.860, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"FileNotFound_FusionLog": "=== Pre-bind state information ===\r\nLOG: DisplayName = Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.28.3.860, Culture=neutral, PublicKeyToken=31bf3856ad364e35\n (Fully-specified)\r\nLOG: Appbase = file:///D:/Program Files (x86)/SiteExtensions/Functions/1.0.11959/\r\nLOG: Initial PrivatePath = D:\\Program Files (x86)\\SiteExtensions\\Functions\\1.0.11959\\bin\r\nCalling assembly : Microsoft.Rest.ClientRuntime.Azure.Authentication, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35.\r\n===\r\nLOG: This bind starts in LoadFrom load context.\r\nWRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().\r\nLOG: Using application configuration file: D:\\Program Files (x86)\\SiteExtensions\\Functions\\1.0.11959\\web.config\r\nLOG: Using host configuration file: D:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\Aspnet.config\r\nLOG: Using machine configuration file from D:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\config\\machine.config.\r\nLOG: Post-policy reference: Microsoft.IdentityModel.Clients.ActiveDirectory, Version=2.28.3.860, Culture=neutral, PublicKeyToken=31bf3856ad364e35\r\nLOG: Attempting download of new URL file:///D:/local/Temporary ASP.NET Files/root/fbd8a7cb/68424d13/Microsoft.IdentityModel.Clients.ActiveDirectory.DLL.\r\nLOG: Attempting download of new URL file:///D:/local/Temporary ASP.NET Files/root/fbd8a7cb/68424d13/Microsoft.IdentityModel.Clients.ActiveDirectory/Microsoft.IdentityModel.Clients.ActiveDirectory.DLL.\r\nLOG: Attempting download of new URL file:///D:/Program Files (x86)/SiteExtensions/Functions/1.0.11959/bin/Microsoft.IdentityModel.Clients.ActiveDirectory.DLL.\r\nLOG: Attempting download of new URL file:///D:/Program Files (x86)/SiteExtensions/Functions/1.0.11959/bin/Microsoft.IdentityModel.Clients.ActiveDirectory/Microsoft.IdentityModel.Clients.ActiveDirectory.DLL.\r\nLOG: Attempting download of new URL file:///D:/local/Temporary ASP.NET Files/root/fbd8a7cb/68424d13/Microsoft.IdentityModel.Clients.ActiveDirectory.EXE.\r\nLOG: Attempting download of new URL file:///D:/local/Temporary ASP.NET Files/root/fbd8a7cb/68424d13/Microsoft.IdentityModel.Clients.ActiveDirectory/Microsoft.IdentityModel.Clients.ActiveDirectory.EXE.\r\nLOG: Attempting download of new URL file:///D:/Program Files (x86)/SiteExtensions/Functions/1.0.11959/bin/Microsoft.IdentityModel.Clients.ActiveDirectory.EXE.\r\nLOG: Attempting download of new URL file:///D:/Program Files (x86)/SiteExtensions/Functions/1.0.11959/bin/Microsoft.IdentityModel.Clients.ActiveDirectory/Microsoft.IdentityModel.Clients.ActiveDirectory.EXE.\r\nLOG: Attempting download of new URL file:///D:/home/site/wwwroot/bin/Microsoft.IdentityModel.Clients.ActiveDirectory.DLL.\r\nWRN: Comparing the assembly name resulted in the mismatch: Major Version\r\nLOG: Attempting download of new URL file:///D:/home/site/wwwroot/bin/Microsoft.IdentityModel.Clients.ActiveDirectory/Microsoft.IdentityModel.Clients.ActiveDirectory.DLL.\r\nLOG: Attempting download of new URL file:///D:/home/site/wwwroot/bin/Microsoft.IdentityModel.Clients.ActiveDirectory.EXE.\r\nLOG: Attempting download of new URL file:///D:/home/site/wwwroot/bin/Microsoft.IdentityModel.Clients.ActiveDirectory/Microsoft.IdentityModel.Clients.ActiveDirectory.EXE.\r\n"}

Project Information: I am using Azure Functions Framework V1 and am targeting .Net 4.6.1

Update 2: Here is the .csroj file with assembly versions and project configurations.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net461</TargetFramework>
    <AzureFunctionsVersion>v1</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.0" />
    <PackageReference Include="Microsoft.Azure.Management.Compute.Fluent" Version="1.14.0" />
    <PackageReference Include="Microsoft.Azure.Management.Redis.Fluent" Version="1.14.0" />
    <PackageReference Include="Microsoft.Azure.Management.ResourceManager.Fluent" Version="1.14.0" />
    <PackageReference Include="Microsoft.Azure.Management.Sql.Fluent" Version="1.14.0" />
    <PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.0.3" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.14" />
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.5.0" />
  </ItemGroup>
  <ItemGroup>
    <Reference Include="Microsoft.CSharp" />
  </ItemGroup>
  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
</Project>

Upvotes: 0

Views: 953

Answers (1)

Jacob Nosal
Jacob Nosal

Reputation: 11

I'll start by admitting that I over complicated this problem. There was no need to retrieve the secret as I can authenticate using TokenCredentials object instantiated with a token from MSI.

For further clarification, ServiceClientCredentials is an abstract class implemented by TokenCredentials. Thus, Azure SDK management clients can be instantiated with the TokenCredentials type.

Upvotes: 1

Related Questions