Как добавить классические виртуальные машины в Azure

Я хотел бы программно отображать и управлять классическими (старыми) виртуальными машинами в 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);            
    }
}

ОБНОВЛЕНИЕ 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:

Я обнаружил, что тот же API используется командой PowerShell Get-AzureVMImage и работает из PowerShell. Powershell просит меня сначала войти в Azure с интерактивными окнами входа по электронной почте и паролю, а запрос использует заголовок Bearer для аутентификации, как мой код.

Если я обнюхаю токен доступа (заголовок Bearer) из сообщения, созданного Powershell, я смогу успешно взаимодействовать с этим API.

Обновление 3: РЕШЕНО, ответьте ниже.

Согласно связанной документации, вам кажется, что вам не хватает необходимого заголовка запроса 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.

Nkosi 22.10.2018 11:24

Любопытно, как не работал другой токен на предъявителя. Может быть, учетные данные?

Nkosi 22.10.2018 13:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
2
985
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Судя по моему тесту, вам нужно получить токен доступа в интерактивном режиме.

А где взять токен «в интерактивном режиме»?

Tomas Kubes 19.10.2018 11:42

Согласно связанной документации, при запросе классического REST API отсутствует требуемый заголовок запроса.

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.

Ссылка Список образов ВМ: заголовки запросов

Чтобы разрешить включение заголовка, создайте перегрузку для запросов GET в 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();

Привет, Nkosi, я добавил заголовок к запросу и проверил запрос в отладчике Fiddler HTTP. Теперь заголовок включен, но он не решает проблему «403 Forbidden».

Tomas Kubes 22.10.2018 13:06

@ qub1n предоставил ли ответ скрипача какие-либо другие подробности, когда возвращается запрещенный статус?

Nkosi 22.10.2018 13:08

Вопрос обновлен, подробный ответ включен. Новый Azure APi может использовать токен или сертификат. Так что я предполагаю то же самое со старым.

Tomas Kubes 22.10.2018 13:23

Я отлично воспроизвел вашу проблему. К сожалению, я не получил рабочий исходный код со старым API, удовлетворяющим ваши потребности.

Хотя я нашел поставщика Microsoft.ClassicCompute вместо обычного используемого Microsoft.Compute, но все еще не смог провести рабочий тест.

Я почти уверен, что вам больше не следует использовать «вручную» старый устаревший API, а следует использовать современные пакеты Microsoft, позволяющие управлять классическими и «обычными» элементами, такими как виртуальные машины или учетные записи хранилища.

Ключевой пакет - Microsoft.Azure.Management.Compute.Fluent

Вы можете найти документацию здесь: https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.management.compute.fluent?view=azure-dotnet

Сообщите мне, если вам все еще понадобится помощь.

Но современный Fluent API не может перечислять и управлять старыми (классическими объектами).

Tomas Kubes 24.10.2018 17:17

Вы уверены? Вдобавок я пробовал с лазурным cli, и это нормально; может можно проверить исходный код (github.com/Azure/azure-cli)? Пример: лазурный список виртуальных машин

Bsquare ℬℬ 24.10.2018 17:33

Финнали у меня все заработало:

Первый открытый Powershell:

Get-AzurePublishSettingsFile

и сохраните этот файл.

затем введите Powershell

Import-AzurePublishSettingsFile [mypublishsettingsfile]

Откройте хранилище сертификатов и найдите импортированный сертификат. И используйте этот сертификат одновременно с учетными данными в HttpClient.

Вот это да. Как вы в конечном итоге пришли к такому процессу? Хорошая находка.

Nkosi 24.10.2018 17:56

Обратный инжиниринг Powershell Команды Azure + Google. Особенно я пробовал все остальное.

Tomas Kubes 24.10.2018 19:36

Хорошо идет. Рад, что ты это понял.

Nkosi 24.10.2018 19:38

qub1n и @Nkosi .. Я вижу, что вы нашли хороший способ решения проблемы 403. Я добавил новый ответ, объясняющий причину проблемы 403 и изменения кода для правильного использования делегированных разрешений. С новыми изменениями кода, которые я предложил, вам не нужно будет использовать секреты клиента из консольного приложения (это угроза безопасности) или команду PowerShell для получения настроек публикации. Более того, вы должны иметь возможность поделиться приложением с любым пользователем, и оно будет работать только с их привилегиями, возвращая только информацию, к которой у них есть доступ.

Rohit Saigal 29.10.2018 17:23

Привет @Rohit, я вижу твой ответ, и он выглядит многообещающим. Проверю сегодня-завтра.

Tomas Kubes 29.10.2018 17:39
Ответ принят как подходящий

1. Причина ошибки 403 при вызове List VM Images API.

Это связано с тем, что ваше приложение, зарегистрированное в Azure AD, неправильно использует делегированные разрешения «API управления службами Windows Azure». Я говорю это, потому что вижу, что ваш код получает токен напрямую, используя идентификатор приложения (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));

Кроме того, поскольку ваше приложение является консольным, было бы лучше зарегистрировать его как «родное» приложение, а не веб-приложение, как оно у вас есть прямо сейчас. Я говорю это потому, что консольные приложения или настольные клиентские приложения, которые могут работать в пользовательских системах, небезопасны для обработки секретов приложений, поэтому вы не должны регистрировать их как «Веб-приложение / API» и не использовать в них какие-либо секреты, поскольку это представляет угрозу безопасности. .

Итак, всего 2 изменения, и все готово. Как я уже говорил ранее, я пробовал их и вижу, что код работает нормально и получил список классических виртуальных машин.

а. Зарегистрируйте свое приложение в Azure AD как собственное приложение (т. Е. Тип приложения должен быть собственным, а не веб-приложением / API), затем в требуемых разрешениях добавьте «Window Azure Service Management API» и проверьте делегированные разрешения в соответствии с предыдущими снимками экрана в пункте 1.

б. Измените способ получения токена, чтобы можно было использовать делегированные разрешения в соответствии с вошедшим пользователем. Конечно, вошедший в систему пользователь должен иметь разрешения на виртуальные машины, которые вы пытаетесь перечислить, или, если у вас несколько пользователей, список будет отражать те виртуальные машины, к которым в настоящее время вошел пользователь, имеющий доступ.

Вот весь рабочий код после того, как я его изменил.

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

Теперь я это понимаю и с вашей помощью заставляю работать. Большое тебе спасибо.

Tomas Kubes 29.10.2018 19:59

@ qub1n, пожалуйста, и рад узнать, что это сработало.

Rohit Saigal 29.10.2018 20:17

Другие вопросы по теме