且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用 azure 活动目录进行身份验证时调用 Azure 管理库 API 时出错

更新时间:2023-12-03 09:50:40

我相信你在正确的轨道上为什么会遇到这个问题.

这是正在发生的事情:

执行Service Management API 的基本权限是委托权限,而不是应用程序权限.换句话说,API 在为其获取令牌的用户的上下文中执行.现在您正在为您的应用程序获取此令牌(由客户端 ID/秘密指定).但是,您的应用程序无权访问您的 Azure 订阅,因为在您的 Azure AD 中为此应用程序创建的用户记录属于 Service Principal 类型.由于此服务主体无权访问您的 Azure 订阅,因此您会收到此禁止错误(我必须说该错误具有误导性,因为您根本没有使用证书).>

您可以做一些事情:

  1. 切换到 Azure 资源管理器 (ARM) API - ARM API 是下一代服务管理 API (SM API),Azure 正朝着这个方向发展.它仅适用于 Azure AD 令牌.如果可能,请使用它来管理您的 Azure 资源(尽管您需要记住,截至今天,并非所有 Azure 资源都可以通过 ARM API 进行管理).他们的做法是使用新的 Azure Portal.有关更多详细信息,请参阅此链接:https://azure.microsoft.com/en-in/documentation/articles/resource-group-create-service-principal-portal/.
  2. 使用 X509 证书 - 您始终可以使用基于 X509 证书的授权来授权您的 SM API 请求.有关更多详细信息,请参阅此链接:https://msdn.microsoft.com/en-us/library/azure/ee460782.aspx#bk_cert.这种方法的缺点是应用程序(或任何有权访问此证书的人)将获得对您的 Azure 订阅的完全访问权限,并且可以在其中执行所有操作(包括删除资源).
  3. 为用户而不是应用程序获取令牌 - 这是您可以采用的另一种方法.本质上要求您的用户通过您的控制台应用程序登录到 Azure AD 并为该用户获取令牌.同样,请记住,此用户必须是您的 Azure 订阅中的共同管理员,并且可以完全访问您的 Azure 订阅,就像使用 SM API 一样,没有 基于角色的访问的概念控制.

My company is looking into reporting on Azure. We only want our customers to give us read only credentials for us to use. I did some research and it looks like Azure Active Directory does just that. So I'm looking to authenticate using a read only Azure Directory Application.

To get me started I was following this blog on using the Management API via Azure Active Directory.

https://msdn.microsoft.com/en-us/library/azure/dn722415.aspx

Aside from the approach show being very unfriendly, it doesn't work =(

I get this error after logging in as a global administrator:

"AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion'."

Did some research and found this style of authentication was for native app and NOT web apps (despite what the blog post saying other wise..). So I made a tweak. My GetAuthorizationHeader now looks like this:

    private static string GetAuthorizationHeader()
    {
        AuthenticationResult result = null;

        var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);

        string clientId = ConfigurationManager.AppSettings["clientId"];
        string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
        ClientCredential clientCred = new ClientCredential(clientId, clientSecret);

        var thread = new Thread(() =>
        {
            result = context.AcquireToken(
              "https://management.core.windows.net/",
              clientCred);
        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Name = "AquireTokenThread";
        thread.Start();
        thread.Join();

        if (result == null)
        {
            throw new InvalidOperationException("Failed to obtain the JWT token");
        }

        string token = result.AccessToken;
        return token;
    }

I am able to get the Access Token (yay). But now when I try to use this with the Azure Management library client I get this error:

"ForbiddenError: The server failed to authenticate the request. Verify that the certificate is valid and is associated with this subscription."

I double checked my permissions in my application. It looked good. I tried giving full access to everything to see if that would have made a difference.

I double checked my tenantId, clientId, and subscriptionId, all looked good.

I made sure the subscription I'm using is pointed to the AD my application is in.

I tried making a new secret key.

My guess is this is the issue: However in this UI I am unable to select any values for that property. I'm unsure if this is the result of a bug or an unfinished feature. Am I missing something here?

Thanks

Here's my full code for reference:

class Program
{
    static void Main(string[] args)
    {
        var token = GetAuthorizationHeader();

        var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"], token);

        using (var computeClient = new ComputeManagementClient(credential))
        {
            var images = computeClient.VirtualMachineOSImages.List();
        }
    }

    private static string GetAuthorizationHeader()
    {
        AuthenticationResult result = null;

        var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);

        string clientId = ConfigurationManager.AppSettings["clientId"];
        string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
        ClientCredential clientCred = new ClientCredential(clientId, clientSecret);

        var thread = new Thread(() =>
        {
            result = context.AcquireToken(
              "https://management.core.windows.net/",
              clientCred);
        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Name = "AquireTokenThread";
        thread.Start();
        thread.Join();

        if (result == null)
        {
            throw new InvalidOperationException("Failed to obtain the JWT token");
        }

        string token = result.AccessToken;
        return token;
    }
}

EDIT: Progress has been made. As I discussed with Gaurav, I needed to ditch the Azure Management Library because as of right now it does not seem to support Azure Resource Manager (ARM) API! So instead I did raw web requests. And it works as intended. If I remove role access off my AD Application I get access denied. When I have it I get back data.

One thing I'm not sure about is making it so my application is auto-adding to new resources.

Also, Is there a way to list Resource Groups that are accessible for my AD Application?

New code:

    class Program
{
    static void Main(string[] args)
    {
        var token = GetAuthorizationHeader();

        string subscriptionId = ConfigurationManager.AppSettings["subscriptionId"];
        string resourceGroupName = ConfigurationManager.AppSettings["resourceGroupName"];
        var uriListMachines = string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/virtualmachines?api-version=2015-05-01-preview", subscriptionId, resourceGroupName);
        var t = WebRequest.Create(uriListMachines);
        t.ContentType = "application/json";
        t.Headers.Add("Authorization", "Bearer " + token);
        var response = (HttpWebResponse)t.GetResponse();

        string result = "";
        using (var reader = new StreamReader(response.GetResponseStream()))
        {
            result = reader.ReadToEnd(); 
        }

        //Original Attempt:
        //var credential = new TokenCloudCredentials(ConfigurationManager.AppSettings["subscriptionId"], token);

        //using (var client = CloudContext.Clients.CreateComputeManagementClient(credential))
        //{
        //    var images = client.VirtualMachineVMImages.List();
        //}
    }

    private static string GetAuthorizationHeader()
    {
        AuthenticationResult result = null;

        var context = new AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["tenantId"]);

        string clientId = ConfigurationManager.AppSettings["clientId"];
        string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
        ClientCredential clientCred = new ClientCredential(clientId, clientSecret);

        var thread = new Thread(() =>
        {
            result = context.AcquireToken(
              "https://management.core.windows.net/",
              clientCred);
        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Name = "AquireTokenThread";
        thread.Start();
        thread.Join();

        if (result == null)
        {
            throw new InvalidOperationException("Failed to obtain the JWT token");
        }

        string token = result.AccessToken;
        return token;
    }
}

EDIT EDIT: I figured out my hung up. Resources created in the OLD portal will get it's own distinct resource group.

From what I can tell you can not add a resource made in the old portal existing resource group (boooo). Resources created in the new portal will be able to assign the resource to an existing group (aka one that gives a role access to my AD Application).

This is such a mess! But at least I know what is going on now.

I believe you're on the right track as to why you're running into this problem.

Here's what's happening:

Essentially permission to execute Service Management API is a delegated permission and not an application permission. In other words, the API is executed in context of the user for which the token is acquired. Now you are getting this token for your application (specified by client id/secret). However your application doesn't have access to your Azure Subscription because the user record created for this application in your Azure AD is of type Service Principal. Since this Service Principal doesn't have access to your Azure Subscription, you're getting this Forbidden Error (I must say that the error is misleading because you're not using certificate at all).

There are a few things you could do:

  1. Switch to Azure Resource Manager (ARM) API - ARM API is the next generation of Service Management API (SM API) and Azure is moving towards this direction only. It exclusively works off of Azure AD token. If possible, make use of that to manage your Azure resources (though you need to keep in mind that as of today not all Azure resources can be managed through ARM API). They way you do it is take your Service Principal and assign it to a particular role using new Azure Portal. Please see this link for more details on this: https://azure.microsoft.com/en-in/documentation/articles/resource-group-create-service-principal-portal/.
  2. Use X509 Certificate - You can always use X509 Certificate based authorization to authorize your SM API requests. Please see this link for more details on that: https://msdn.microsoft.com/en-us/library/azure/ee460782.aspx#bk_cert. The downside of this approach is that the application (or whosoever has access to this certificate) will get full access to your Azure Subscription and can do everything there (including deleting resources).
  3. Acquire token for a user instead of an application - This is another approach you can take. Essentially ask your users to login into Azure AD through your console application and acquire token for that user. Again, please keep in mind that this user must be a Co-Admin in your Azure Subscription and will have full access to your Azure Subscription as with SM API there's no concept of Role-based access control.