如何在 Azure 中列出经典虚拟机

How to list Virtual Machines Classic in Azure

我想以编程方式列出和控制 Azure 中的经典虚拟机(旧虚拟机)。对于管理,这不是问题,有图书馆,其余的 API 正在工作,但是一旦我 calling the old 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);            
    }
}

更新 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>

更新二:

我发现 powershell 命令 Get-AzureVMImage 使用了相同的 API 并且它在 powershell 中运行。 Powershell 首先要求我通过电子邮件和密码使用交互式登录 windows 登录到 Azure,请求使用 Bearer header 像我的代码一样进行身份验证。

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

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

根据我的测试,您需要以交互方式获取访问令牌。

根据链接文档,您在请求经典 REST API

时似乎缺少必需的请求 header

x-ms-version - Required. Specifies the version of the operation to use for this request. This header should be set to 2014-02-01 or higher.

引用List VM Images: Request Headers

为了允许包含 header,在 AzureRestClient

中为 GET 请求创建重载
public async Task<string> GetRequestAsync(string url, Dictionary<string, string> headers) {
    var request = new HttpRequestMessage(HttpMethod.Get, url);
    if (headers != null)
        foreach (var header in headers) {
            request.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }
    var response = await _client.SendAsync(request);
    return await response.Content.ReadAsStringAsync();
}

并在调用 apiOld

时包含所需的 header
var headers = new Dictionary<string, string>();
headers["x-ms-version"] = "2014-02-01";

string resultOld = client.GetRequestAsync(apiOld, headers).GetAwaiter().GetResult();

我已经完美复现了你的问题。 不幸的是,我没有得到旧 API 满足您需求的工作源代码。

虽然我找到了 Microsoft.ClassicCompute 供应商,而不是通常使用的 Microsoft.Compute 供应商,但仍然无法进行有效测试。

我很确定你不应该再“手动”使用旧的过时的 API,而应该使用允许管理 Classic 和 "Normal" 个元素,例如虚拟机或存储帐户。

密钥包是Microsoft.Azure.Management.Compute.Fluent

您可以在此处找到文档:https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.management.compute.fluent?view=azure-dotnet

如果您还需要帮助,请告诉我。

最后我让它工作了:

首先打开 Powershell:

Get-AzurePublishSettingsFile

并保存该文件。

然后输入 Powershell

Import-AzurePublishSettingsFile [mypublishsettingsfile]

打开证书存储并找到导入的证书。并使用该证书 同时在 HttpClient 中使用凭据。

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

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

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

我能够使用您的代码重现 403 错误,然后能够使其正常工作,return 经典 VM 的列表有一些变化。接下来我将解释所需的更改。

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

2。使其工作所需的更改

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

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

此外,由于您的应用程序是控制台应用程序,因此最好将其注册为 "Native" 应用程序,而不是像您现在拥有的 Web 应用程序。我这样说是因为控制台应用程序或基于桌面客户端的应用程序可以 运行 在用户系统上处理应用程序秘密是不安全的,所以你不应该将它们注册为 "Web app / API" 并且不要在其中使用任何秘密安全风险。

总的来说,有 2 个变化,你应该可以开始了。正如我之前所说,我已经尝试过这些并且可以看到代码运行良好并获得了经典 VM 的列表。

一个。在 Azure AD 中将您的应用程序注册为本机应用程序(即应用程序类型应该是本机应用程序而不是 Web 应用程序/API),然后在所需的权限中添加 "Window Azure Service Management API" 并根据之前的屏幕截图检查委派权限第 1 点

b。更改获取令牌的方式,以便可以根据登录用户使用委托权限。当然,登录用户应该对您尝试列出的 VM 具有权限,或者如果您有多个用户,该列表将反映当前登录用户有权访问的那些 VM。

这是我修改后的全部工作代码。

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);
        }
    }
}