我想以编程方式列出和控制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>
更新2:
我发现powershell命令Get-AzureVMImage使用了相同的API,并且可以从powershell运行。 Powershell首先要求我通过电子邮件和密码通过交互式登录窗口登录Azure,然后该请求使用Bearer标头进行身份验证,类似于我的代码。
如果我从Powershell创建的通信中嗅探访问令牌(Bearer标头),则可以成功与该API通信。
更新3:已解决,在下面回答。
答案 0 :(得分:3)
根据链接的文档,您在请求经典REST API时似乎缺少必需的请求标头
x-ms-version -必需。指定用于此请求的操作的版本。此标头应设置为 2014-02-01 或更高版本。
引用List VM Images: Request Headers
要允许包含标头,请在AzureRestClient
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
var headers = new Dictionary<string, string>();
headers["x-ms-version"] = "2014-02-01";
string resultOld = client.GetRequestAsync(apiOld, headers).GetAwaiter().GetResult();
答案 1 :(得分:2)
最后我得到了它:
第一个打开Powershell:
Get-AzurePublishSettingsFile
并保存该文件。
然后在Powershell中输入
Import-AzurePublishSettingsFile [mypublishsettingsfile]
打开证书存储区并找到导入的证书。并使用该证书 同时使用HttpClient中的凭据。
答案 2 :(得分:2)
这是因为您的Azure AD注册应用程序未正确使用“ Windows Azure Service Management API”委派权限。之所以这样说,是因为我看到您的代码正在直接使用应用程序身份(ClientCredential)而不是作为用户来获取令牌。
请参见下面的屏幕截图。窗口Azure服务管理API显然不提供任何应用程序权限,仅可以使用的是委派权限。如果您想进一步了解这两种权限之间的区别,请阅读 Permissions in Azure AD 。简而言之,在使用委派权限时,将向应用程序委派权限,以在调用API时充当登录用户。因此必须有一个登录用户。
我能够使用您的代码重现403错误,然后使其能够工作并返回经过更改的经典VM列表。接下来,我将解释所需的更改。
转到您的Azure AD>应用程序注册>您的应用程序>设置>所需权限:
更改将是获取令牌作为登录用户,而不是直接使用应用程序的clientId和secret。由于您的应用程序是控制台应用程序,因此执行以下操作很有意义,这将提示用户输入凭据:
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto));
此外,由于您的应用程序是控制台应用程序,因此最好将其注册为“本机”应用程序,而不是像现在那样拥有Web应用程序。我之所以这样说是因为可以在用户系统上运行的控制台应用程序或基于桌面客户端的应用程序并不安全,无法处理应用程序机密,因此,您不应将它们注册为“ Web应用程序/ API”,也不要在其中使用任何机密,因为这存在安全风险
因此,总体来说,有2处更改,您应该一切顺利。如前所述,我已经尝试了这些方法,并且可以看到代码正常工作并获得了经典VM的列表。
a。在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);
}
}
}
答案 3 :(得分:0)
根据我的测试,您需要以交互方式获取访问令牌。
答案 4 :(得分:0)
我已完美地转载了您的问题。 不幸的是,旧API无法满足您的需要,因此我无法获得有效的源代码。
尽管我找到了Microsoft.ClassicCompute提供程序,而不是通常使用的Microsoft.Compute提供程序,但是仍然无法进行有效的测试。
我很确定您不应该再“ 手动”使用旧的过时的API,而应该使用现代的Microsoft软件包来管理“经典”和“常规”元素,例如虚拟机或存储帐户。
密钥包为 Microsoft.Azure.Management.Compute.Fluent
让我知道您是否仍然需要帮助。