从守护程序创建新的 Azure SQL 服务器

Creating new Azure SQL Server from daemon

如何使用 Azure SDK 从守护程序(技术上是队列处理器)的资源组中创建新的 Azure SQL 服务器?我遇到的问题是尝试创建时出现授权错误。这是例外情况:

Hyak.Common.CloudException AuthorizationFailed: The client 
'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' with object id 'XXXXXXXX- 
XXXX-XXXX-XXXX-XXXXXXXXXXXX' does not have authorization to 
perform action 'Microsoft.Sql/servers/write' over scope 
'/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/{myResourceGroupName}/providers/Microsoft 
.Sql/servers/{newServerName}'.

*(The actual exception has real values in each of the 
placeholders.)*

我正在使用 Microsoft.Azure.Management.Sql NuGet 包,版本 0.38.0-prerelease(当前最新可用)。

我之前尝试使用 CertificateCloudCredentials 进行更改,但它产生了以下异常:

 Hyak.Common.CloudException AuthenticationFailed: Authentication 
 failed. The 'Authorization' header is not present or provided 
 in an invalid format.

现在我正在使用通过以下方式获得的TokenCloudCredentials

AuthenticationContext aContext = new AuthenticationContext(string.Format(
            anApiConfiguration.LoginUriFormat,
            anApiConfiguration.TenantId));
ClientCredential aClientCredential = new ClientCredential(
            anApiConfiguration.ClientId, 
            anApiConfiguration.ClientSecretKey);
AuthenticationResult aTokenResponse = await aContext.AcquireTokenAsync(
            anApiConfiguration.BaseResourceUrl, 
            aClientCredential);

由此我成功获得了令牌。然后我按如下方式使用该令牌:

SqlManagementClient sql = new SqlManagementClient(anAuthToken);
ServerCreateOrUpdateProperties someProperties = 
    new ServerCreateOrUpdateProperties {
        AdministratorLogin = someSettings.AdminAccountName,
        AdministratorLoginPassword = someSettings.AdminPassword,
        Version = "12.0"
};
ServerGetResponse aCreateResponse = 
    await sql.Servers.CreateOrUpdateAsync(
        someSettings.GetResourceGroup(aRegionType),
        aServerName, 
        new ServerCreateOrUpdateParameters(someProperties, aRegion));

最后一行,即 CreateOrUpdateAsync 调用,是在此 post.

顶部产生异常的原因

在 Azure Active Directory 中,客户端应用程序被授予 Access Azure Service Management(预览版) 委派权限(该应用程序唯一存在的权限)Windows Azure 服务管理 API 应用程序。 (如果重要的话,我还向客户端应用程序授予 Windows Azure Active Directory 应用程序的所有可用权限。)

我写了一个控制台应用程序来测试各种场景。虽然我需要尝试更多案例,但目前我实施的案例中只有两个成功:

  • NativeClientApp 与 ClientId_RedirectUri_PromptBehavior
  • 带有 ClientId_RedirectUri_PromptBehavior_UserIdentifier
  • 的 NativeClientApp

源码在下面,有兴趣的可以看看。另外,我现在只测试资源组的创建,因为这比创建 Sql 服务器实例要简单得多。两者似乎都会产生相同的错误。

初步结果

结果输出显示案例、结果和继续的提示。

WebApp with ClientCertificate
The method or operation is not implemented.
Press any key to continue.

WebApp with ClientAssertion
The method or operation is not implemented.
Press any key to continue.

WebApp with ClientCredential
AuthorizationFailed: The client '7a31a564-20ba-4ac1-a2ee-4f5e35a70dcc' with object id '7a31a564-20ba-4ac1-a2ee-4f5e35a70dcc' does not have authorization to perform action 'Microsoft.Resources/subscriptions/resourcegroups/write' ov
er scope '/subscriptions/14929cfc-3501-48cf-a5c9-b24a7daaf694/resourcegroups/MY_RESOURCE_GROUP_NAME635776010237527878'.
Press any key to continue.

WebApp with ClientId_UserAssertion
The method or operation is not implemented.
Press any key to continue.

WebApp with ClientCredential_UserAssertion
AADSTS50027: Invalid JWT token. AADSTS50027: Invalid JWT token. Token format not valid.
Trace ID: a64f0683-23ae-4461-8546-55293f7ff1d3
Correlation ID: 62cc80e9-f013-4a74-8031-3294e69d4478
Timestamp: 2015-09-12 03:43:57Z
Press any key to continue.

WebApp with ClientAssertion_UserAssertion
The method or operation is not implemented.
Press any key to continue.

WebApp with ClientCertificate_UserAssertion
The method or operation is not implemented.
Press any key to continue.

WebApp with ClientId_RedirectUri
The method or operation is not implemented.
Press any key to continue.

WebApp with ClientId_UserCredential
missing_federation_metadata_url: Federation Metadata Url is missing for federated user. This user type is unsupported.
Press any key to continue.

WebApp with ClientId_RedirectUri_PromptBehavior
AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion'.
Trace ID: 06fde160-bd2b-4f16-b49e-0f0ff8e17f48
Correlation ID: baabb83f-cebb-48ba-b2be-1efb53ec3121
Timestamp: 2015-09-12 03:44:21Z
Press any key to continue.

WebApp with ClientId_RedirectUri_PromptBehavior_UserIdentifier
AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion'.
Trace ID: 6110ef02-a6b0-4e41-b0e5-db97b13c66ce
Correlation ID: e6f6526a-8395-480a-8ac7-b75903b324d9
Timestamp: 2015-09-12 03:44:30Z
Press any key to continue.

WebApp with ClientId_RedirectUri_PromptBehavior_UserIdentifier_ExtraQueryParams
The method or operation is not implemented.
Press any key to continue.

NativeClientApp with ClientCertificate
The method or operation is not implemented.
Press any key to continue.

NativeClientApp with ClientAssertion
The method or operation is not implemented.
Press any key to continue.

NativeClientApp with ClientCredential
Value cannot be null.
Parameter name: clientSecret
Press any key to continue.

NativeClientApp with ClientId_UserAssertion
The method or operation is not implemented.
Press any key to continue.

NativeClientApp with ClientCredential_UserAssertion
Value cannot be null.
Parameter name: clientSecret
Press any key to continue.

NativeClientApp with ClientAssertion_UserAssertion
The method or operation is not implemented.
Press any key to continue.

NativeClientApp with ClientCertificate_UserAssertion
The method or operation is not implemented.
Press any key to continue.

NativeClientApp with ClientId_RedirectUri
The method or operation is not implemented.
Press any key to continue.

NativeClientApp with ClientId_UserCredential
missing_federation_metadata_url: Federation Metadata Url is missing for federated user. This user type is unsupported.
Press any key to continue.

NativeClientApp with ClientId_RedirectUri_PromptBehavior
Success! Created MY_RESOURCE_GROUP_NAME635776011040240760
Press any key to continue.

NativeClientApp with ClientId_RedirectUri_PromptBehavior_UserIdentifier
Success! Created MY_RESOURCE_GROUP_NAME635776011079693849
Press any key to continue.

NativeClientApp with ClientId_RedirectUri_PromptBehavior_UserIdentifier_ExtraQueryParams
The method or operation is not implemented.
Press any key to continue.

Testing complete.:)

Program.cs

using System;
using Microsoft.Azure;
using Microsoft.Azure.Management.Resources;
using Microsoft.Azure.Management.Resources.Models;
using Microsoft.Azure.Management.Sql;
using Microsoft.Azure.Management.Sql.Models;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace CreateNewAzureSQLServer
{
    class Program
    {
        private static string

            domain = "MY_DOMAIN.onmicrosoft.com",
            resource = "https://management.azure.com/",
            subscriptionId = "xxxxx-xxxxx-xxxxx-xxxxx",

            // web
            clientId_web = "xxxxx-xxxxx-xxxxx-xxxxx",
            clientSecret_web = "xxxxx=",
            redirectUri_web = "http://myWebApp",

            // native
            clientId_native = "xxxxx-xxxxx-xxxxx-xxxxx",
            clientSecret_native = string.Empty,
            redirectUri_native = "http://myNativeClientApp",

            // adminstrator
            userName = "MY_USERNAME",
            userPassword = "MY_PASSWORD",

            // create
            adminAccountName = "MY_ADMIN_ACCOUNT_NAME",
            adminAccountPwd = "MY_ACCOUNT_ADMIN_PWD",
            resourceGroupName = "MY_RESOURCE_GROUP_NAME",
            serverName = "MY_SERVER_NAME",
            location = "West US";

        private static AuthenticationResult GetAccessToken(
            string clientId, string redirectUri, string clientSecret, AuthType type)
        {
            var authority = "https://login.windows.net/" + domain;
            var authContext = new AuthenticationContext(authority);
            authContext.TokenCache.Clear();
            AuthenticationResult token = null;

            switch (type)
            {
                case AuthType.ClientCertificate:
                    throw new NotImplementedException();
                    break;

                case AuthType.ClientAssertion:
                    throw new NotImplementedException();
                    break;

                case AuthType.ClientCredential:                       
                    token = authContext.AcquireToken(resource,
                        new ClientCredential(clientId, clientSecret));
                    break;

                case AuthType.ClientId_UserAssertion:
                    throw new NotImplementedException();
                    break;

                case AuthType.ClientCredential_UserAssertion:
                    token = authContext.AcquireToken(resource,
                        new ClientCredential(clientId, clientSecret),
                        new UserAssertion(userPassword, "username", userName));
                    break;

                case AuthType.ClientAssertion_UserAssertion:
                    throw new NotImplementedException();
                    break;

                case AuthType.ClientCertificate_UserAssertion:
                    throw new NotImplementedException();
                    break;

                case AuthType.ClientId_RedirectUri:
                    throw new NotImplementedException();
                    break;

                case AuthType.ClientId_UserCredential:
                    token = authContext.AcquireToken(resource,
                        clientId,
                        new UserCredential(userName, userPassword));
                    break;

                case AuthType.ClientId_RedirectUri_PromptBehavior:
                    token = authContext.AcquireToken(resource,
                        clientId,
                        new Uri(redirectUri),
                        PromptBehavior.Auto);
                    break;

                case AuthType.ClientId_RedirectUri_PromptBehavior_UserIdentifier:                        
                    var cred = new UserCredential(userName);
                    token = authContext.AcquireToken(resource,
                        clientId,
                        new Uri(redirectUri),
                        PromptBehavior.Auto,
                        new UserIdentifier(cred.UserName, UserIdentifierType.RequiredDisplayableId));
                    break;

                case AuthType.ClientId_RedirectUri_PromptBehavior_UserIdentifier_ExtraQueryParams:
                    throw new NotImplementedException();
                    break;

                default:
                    break;
            };

            return token;
        }

        static void CreateSqlServer(TokenCloudCredentials creds)
        {
            var client = new SqlManagementClient(creds);
            var someProperties = new ServerCreateOrUpdateProperties
            {
                AdministratorLogin = adminAccountName,
                AdministratorLoginPassword = adminAccountPwd,
                Version = "12"
            };

            var parameters = new ServerCreateOrUpdateParameters(someProperties, location);
            ServerGetResponse aCreateResponse
                = client.Servers.CreateOrUpdate(resourceGroupName, serverName, parameters);
        }

        static string CreateResourceGroup(TokenCloudCredentials creds)
        {
            var uniqueResourceGroupName = resourceGroupName + DateTime.Now.Ticks.ToString();
            var resourceClient = new ResourceManagementClient(creds);
            var resourceGroupParameters = new ResourceGroup()
            {
                Location = location
            };
            var resourceGroupResult = resourceClient
                .ResourceGroups
                .CreateOrUpdate(uniqueResourceGroupName, resourceGroupParameters);

            return uniqueResourceGroupName;
        }

        static void Main(string[] args)
        {
            foreach (AppType appType in Enum.GetValues(typeof(AppType)))
            {
                var clientId = appType == AppType.WebApp ? clientId_web : clientId_native;
                var clientSecret = appType == AppType.WebApp ? clientSecret_web : clientSecret_native;
                var redirectUri = appType == AppType.WebApp ? redirectUri_web : redirectUri_native;

                foreach (AuthType authType in Enum.GetValues(typeof(AuthType)))
                {
                    try
                    {
                        Console.WriteLine(appType.ToString() + " with " + authType.ToString());
                        var token = GetAccessToken(clientId, redirectUri, clientSecret, authType);
                        var creds = new TokenCloudCredentials(subscriptionId, token.AccessToken);
                        var resourceGroupName = CreateResourceGroup(creds);
                        Console.WriteLine("Success! Created " + resourceGroupName);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }

                    Console.WriteLine("Press any key to continue.");
                    Console.ReadLine();
                }
            }

            Console.WriteLine("Testing complete.:)");
            Console.ReadLine();

            //CreateSqlServer(creds);
        }

        enum AppType
        {
            WebApp,
            NativeClientApp
        }

        enum AuthType
        {
            ClientCertificate,
            ClientAssertion,
            ClientCredential,
            ClientId_UserAssertion,
            ClientCredential_UserAssertion,
            ClientAssertion_UserAssertion,
            ClientCertificate_UserAssertion,
            ClientId_RedirectUri,
            ClientId_UserCredential,
            ClientId_RedirectUri_PromptBehavior,
            ClientId_RedirectUri_PromptBehavior_UserIdentifier,
            ClientId_RedirectUri_PromptBehavior_UserIdentifier_ExtraQueryParams
        }
    }
}

packages.config

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Hyak.Common" version="1.0.2" targetFramework="net452" />
  <package id="Microsoft.Azure.Common" version="2.1.0" targetFramework="net452" />
  <package id="Microsoft.Azure.Common.Authentication" version="1.1.5-preview" targetFramework="net452" />
  <package id="Microsoft.Azure.Common.Dependencies" version="1.0.0" targetFramework="net452" />
  <package id="Microsoft.Azure.Management.Resources" version="2.18.7-preview" targetFramework="net452" />
  <package id="Microsoft.Azure.Management.Sql" version="0.38.0-prerelease" targetFramework="net452" />
  <package id="Microsoft.Bcl" version="1.1.9" targetFramework="net452" />
  <package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net452" />
  <package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net452" />
  <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="2.18.206251556" targetFramework="net452" />
  <package id="Microsoft.Net.Http" version="2.2.22" targetFramework="net452" />
  <package id="Microsoft.Rest.ClientRuntime" version="1.2.0" targetFramework="net452" />
  <package id="Microsoft.Rest.ClientRuntime.Azure.Authentication" version="0.9.3" targetFramework="net452" />
  <package id="Newtonsoft.Json" version="6.0.4" targetFramework="net452" />
</packages>

我想我已经弄清楚问题是什么以及如何解决它(我也能够在我的工作计算机上重现它)。

原因

在示例应用中,获取身份验证令牌的默认方法似乎使用了错误的帐户。例如,我的订阅和应用程序在我的 example@outlook.com 电子邮件帐户下,但是当我从我的工作计算机 运行 控制台应用程序时,该应用程序获取用户名@company.com 的令牌并尝试执行该帐户下的操作。似乎用于获取身份验证令牌的代码正在使用计算机(IE?)中的一些缓存帐户信息。

您看到的错误消息与 RBAC(基于角色的访问控制)有关,它限制经过身份验证但未经授权的帐户对您的资源执行操作 (https://azure.microsoft.com/en-us/documentation/articles/role-based-access-control-configure/)。

解决方案

您可以稍微更改代码以告诉它在执行操作时使用哪个帐户。将示例应用程序中的 "GetAccessTokenUsingUserCredentials" 方法更改为如下所示:

    private static AuthenticationResult GetAccessTokenUsingUserCredentials(UserCredential userCredential)
    {
        AuthenticationContext authContext = new AuthenticationContext
            ("https://login.windows.net/" /* AAD URI */
            + "YOU.onmicrosoft.com" /* Tenant ID or AAD domain */);

        AuthenticationResult token = authContext.AcquireToken(
            "https://management.azure.com/"/* the Azure Resource Management endpoint */,
            "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" /* application client ID from AAD*/,
            new Uri("XXXX" /* redirect URI */,
            PromptBehavior.Auto,
            new UserIdentifier(userCredential.UserName, UserIdentifierType.RequiredDisplayableId));

        return token;
    }

并在主函数中将获取令牌的行更改为:

        var token = GetAccessTokenUsingUserCredentials(new UserCredential("<desired_account_email>"));

您应该可以稍微修改一下,也可以输入密码,这样 AAD 登录框就不会弹出。

希望对你有用!

编辑:以下是让它在 Web 应用程序中运行的方法:

  1. 从 Web 应用程序的配置页面获取您的客户端 ID 和密钥(您需要 select 密钥的持续时间,然后单击保存以生成它)
  2. 将应用程序中的验证码更改为如下所示:

    private static AuthenticationResult GetAccessTokenForWebApp()
    {
        AuthenticationContext authContext = new AuthenticationContext
            ("https://login.windows.net/" /* AAD URI */
            + "YOU.onmicrosoft.com" /* Tenant ID or AAD domain */);
    
        ClientCredential cc = new ClientCredential("<client_id>", "<key>");
    
        AuthenticationResult token = authContext.AcquireToken(
            "https://management.azure.com/"/* the Azure Resource Management endpoint */,
            cc);
    
        return token;
    }
    
  3. 此时,如果您尝试 运行 该应用程序,您将收到如下所示的身份验证错误:

Hyak.Common.CloudException AuthorizationFailed: The client 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' with object id 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' does not have authorization to perform action 'Microsoft.Sql/servers/write' over scope '/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/{myResourceGroupName}/providers/Microsoft.Sql/servers/{newServerName}'.

这是因为您需要为 application/service 主体添加一个 RBAC 角色。从您需要复制 "Object ID" 的错误消息中,这是将用于角色分配的 AD 对象 ID。

  1. 获取 Windows Azure 的最新 powershell cmdlet:http://azure.microsoft.com/en-us/downloads/
  2. 安装 powershell 后 运行 以下内容:

    Switch-AzureMode AzureResourceManager #_this will be deprecated soon_
    Add-AzureAccount # you will be prompted for your credentials
    New-AzureRoleAssignment -ObjectId <your_object_id> -RoleDefinitionName <role_name> 
    # You can use "Contributor" to give the web app access to basically everything.
    # You can look in portal.azure.com for the roles that are available
    
    1. 当上面 运行s 你应该看到对象 ID 被添加到角色的输出。

您的 Web 应用程序现在应该可以运行了!

一些额外阅读:

对于服务主体/应用程序登录:http://azure.microsoft.com/en-us/documentation/articles/resource-group-authenticate-service-principal/

代为登录:https://msdn.microsoft.com/en-us/library/azure/dn790557.aspx