如何在Azure中列出Virtual Machines Classic

Tom*_*bes 5 c# rest credentials azure azure-active-directory

我想以编程方式列出和控制Azure中的经典(旧版)虚拟机.对于托管它没有问题,有库和其余的API正在工作,但一旦我调用旧API列出经典,我得到403(禁止).

代码好吗?我是否需要在其他地方管理旧API的凭据?

我的代码在这里:

static void Main(string[] args)
{
    string apiNew = "https://management.azure.com/subscriptions/xxxxxxxxxxxxxxxxxxxxxxxx/providers/Microsoft.Compute/virtualMachines?api-version=2018-06-01";
    string apiOld = "https://management.core.windows.net/xxxxxxxxxxxxxxxxxxxxxxxx/services/vmimages"

    AzureRestClient client = new AzureRestClient(credentials.TenantId, credentials.ClientId, credentials.ClientSecret);

    //OK - I can list the managed VMs.         
    string resultNew = client.GetRequestAsync(apiNew).Result;

    // 403 forbidden
    string resultOld = client.GetRequestAsync(apiOld).Result;        
}

public class AzureRestClient : IDisposable
{
    private readonly HttpClient _client;

    public AzureRestClient(string tenantName, string clientId, string clientSecret)
    {
        _client = CreateClient(tenantName, clientId, clientSecret).Result;
    }

    private async Task<string> GetAccessToken(string tenantName, string clientId, string clientSecret)
    {
        string authString = "https://login.microsoftonline.com/" + tenantName;
        string resourceUrl = "https://management.core.windows.net/";

        var authenticationContext = new AuthenticationContext(authString, false);
        var clientCred = new ClientCredential(clientId, clientSecret);
        var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientCred);
        var token = authenticationResult.AccessToken;

        return token;
    }

    async Task<HttpClient> CreateClient(string tenantName, string clientId, string clientSecret)
    {
        string token = await GetAccessToken(tenantName, clientId, clientSecret);
        HttpClient client = new HttpClient();
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);        
        return client;
    }

    public async Task<string> GetRequestAsync(string url)
    {           
        return await _client.GetStringAsync(url);            
    }
}
Run Code Online (Sandbox Code Playgroud)

更新1:

回复详情:

HTTP/1.1 403 Forbidden
Content-Length: 288
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 22 Oct 2018 11:03:40 GMT

HTTP/1.1 403 Forbidden
Content-Length: 288
Content-Type: application/xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Mon, 22 Oct 2018 11:03:40 GMT

<Error xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Code>ForbiddenError</Code>
    <Message>The server failed to authenticate the request.
      Verify that the certificate is valid and is associated with this subscription.</Message>
</Error>
Run Code Online (Sandbox Code Playgroud)

更新2:

我发现powershell命令Get-AzureVMImage使用了相同的API,它正在使用PowerShell.Powershell首先要求我通过电子邮件和密码使用交互式登录窗口登录Azure,并且请求使用Bearer标头来验证我的代码.

如果我从Powershell创建的通信中嗅探访问令牌(Bearer标头),我可以成功地与该API通信.

更新3:已解决,回答如下.

Roh*_*gal 3

1. 调用List VM Images API时出现403的原因

这是因为您的 Azure AD 注册应用程序未正确使用“Windows Azure 服务管理 API”委派权限。我这样说是因为我看到您的代码是直接使用应用程序身份(ClientCredential)而不是作为用户获取令牌。

请参阅下面的屏幕截图。Window Azure Service Management API显然不提供任何应用程序权限,唯一可以使用的就是委托权限。如果您想详细了解这两种权限之间的区别,请阅读Azure AD 中的权限。简而言之,使用委派权限时,应用程序会被委派权限,在调用 API 时充当登录用户。所以必须有一个登录用户。

我能够使用您的代码重现 403 错误,然后能够使其工作并返回经过一些更改的经典虚拟机列表。接下来我将解释所需的更改。

转到您的 Azure AD > 应用程序注册 > 您的应用程序 > 设置 > 所需权限:

在此输入图像描述

在此输入图像描述

2. 使其发挥作用所需的更改

更改将是作为登录用户获取令牌,而不是直接使用应用程序的 clientId 和密钥。由于您的应用程序是控制台应用程序,因此执行以下操作是有意义的,这将提示用户输入凭据:

var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto));
Run Code Online (Sandbox Code Playgroud)

另外,由于您的应用程序是控制台应用程序,因此最好将其注册为“本机”应用程序,而不是像现在这样的 Web 应用程序。我这样说是因为可以在用户系统上运行的控制台应用程序或基于桌面客户端的应用程序无法安全地处理应用程序机密,因此您不应将它们注册为“Web 应用程序/API”并且不要在其中使用任何机密,因为这是安全风险。

总的来说,只要进行 2 处更改,您就可以开始了。正如我之前所说,我已经尝试过这些,并且可以看到代码运行良好并获得了经典虚拟机的列表。

A。在 Azure AD 中将您的应用程序注册为本机应用程序(即应用程序类型应该是本机而不是 Web 应用程序/API),然后在所需权限中添加“Window Azure 服务管理 API”并按照第 1 点中的先前屏幕截图检查委派权限

在此输入图像描述

b. 更改获取token的方式,以便可以按照登录用户使用委托权限。当然,登录用户应该有权访问您尝试列出的虚拟机,或者如果您有多个用户,则列表将反映当前登录用户有权访问的虚拟机。

这是我修改后的完整工作代码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http;
using System.Net.Http.Headers;

namespace ListVMsConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            string tenantId = "xxxxxx";
            string clientId = "xxxxxx";
            string redirectUri = "https://ListClassicVMsApp";

            string apiNew = "https://management.azure.com/subscriptions/xxxxxxxx/providers/Microsoft.Compute/virtualMachines?api-version=2018-06-01";
            string apiOld = "https://management.core.windows.net/xxxxxxxx/services/vmimages";

            AzureRestClient client = new AzureRestClient(tenantId, clientId, redirectUri);

            //OK - I can list the managed VMs.         
            //string resultNew = client.GetRequestAsync(apiNew).Result;

            // 403 forbidden - should work now
            string resultOld = client.GetRequestAsync(apiOld).Result;
        }

    }

    public class AzureRestClient
    {
        private readonly HttpClient _client;

        public AzureRestClient(string tenantName, string clientId, string redirectUri)
        {
            _client = CreateClient(tenantName, clientId, redirectUri).Result;
        }

        private async Task<string> GetAccessToken(string tenantName, string clientId, string redirectUri)
        {
            string authString = "https://login.microsoftonline.com/" + tenantName;
            string resourceUrl = "https://management.core.windows.net/";

            var authenticationContext = new AuthenticationContext(authString, false);
            var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto));

            return authenticationResult.AccessToken;
        }

        async Task<HttpClient> CreateClient(string tenantName, string clientId, string redirectUri)
        {
            string token = await GetAccessToken(tenantName, clientId, redirectUri);
            HttpClient client = new HttpClient();
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
            client.DefaultRequestHeaders.Add("x-ms-version", "2014-02-01");
            return client;
        }

        public async Task<string> GetRequestAsync(string url)
        {
            return await _client.GetStringAsync(url);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)