Search code examples
azureazure-api-management

Why I'm I getting a 403 using Azure Service Management API?


I want to use the Azure API Management (management.core.windows.net) to reboot role instance (ref to Microsoft documentation: https://learn.microsoft.com/en-us/rest/api/compute/cloudservices/rest-reboot-role-instance) but I'm getting a 403 as a response.

Request:

https://management.core.windows.net/{subscription-id}/services/hostedservices/{hosted-service}/deploymentslots/staging/roleinstances/{role-instance-name}?comp=reboot`

Headers:
- Authorization: Bearer {token}
- Content-Type: application/xml
- x-ms-version: 2010-10-28
- Content-Length: 0

Body: Empty

Response Body:

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

I get the Authentication - Bearer Token by calling (ref to Microsoft documentation: https://learn.microsoft.com/en-us/azure/active-directory/azuread-dev/v1-oauth2-client-creds-grant-flow#service-to-service-access-token-request):

https://login.microsoftonline.com/{tenant_id}/oauth2/token

Headers:
- Content-Type: application/x-www-form-urlencoded

Body: 
- grant_type: client_credentials,
- client_id: {client_id}
- client_secret: {client_secret}
- resource: https://management.core.windows.net/

Any idea? Any missing configuration on the request or Azure Portal side? Is the management.core.windows.net deprecated since I can use the management.azure.com?

Notes:

  • I already configured the permissions on the Azure side: I created an app registration for this, with a secret used to give permissions as contributor;
  • The management.azure.com API works with the Bearer Token. I can access other resources such as https://management.azure.com/subscriptions/{subscription-id}/resourcegroups?api-version=2017-05-10 but I can't access the https://management.core.windows.net/{subscription-id}/services/hostedservices resources.
  • I'm testing this on Postman.

SOLUTION

The problem was related to the certificate configuration

$cert = New-SelfSignedCertificate -Subject "CN=Azure Management API" -CertStoreLocation "cert:\LocalMachine\My" -KeyLength 2048 -KeySpec "KeyExchange" -NotAfter (Get-Date).AddMonths(360)
$password = ConvertTo-SecureString -String "strong-password-here" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath ".\azure-management-api.pfx" -Password $password
Export-Certificate -Type CERT -Cert $cert -FilePath .\azure-management-api.cer

Attention to the importance of the certificate to be a .pfx

CODE

   var cert = new X509Certificate2( File.ReadAllBytes( "your-certificate-path.pfx" ), "your_password" );
            var httpClientHandler = new HttpClientHandler
            {
                UseProxy = false,
                ClientCertificateOptions = ClientCertificateOption.Manual
            };
            httpClientHandler.ClientCertificates.Add( cert );
            var httpClient = new HttpClient( httpClientHandler );
            httpClient.DefaultRequestHeaders.Add( "Accept", "application/xml" );
            httpClient.DefaultRequestHeaders.Add( "Host", "management.core.windows.net" );
            httpClient.DefaultRequestHeaders.Add( "x-ms-version", "2010-10-28" );
            var uri = $"https://management.core.windows.net/{subscriptionId}/services/hostedservices";
            Console.WriteLine( $"GET {uri} [{httpClient.DefaultRequestVersion}]" );
            foreach ( var header in httpClient.DefaultRequestHeaders )
            {
                Console.WriteLine( $"{header.Key} {header.Value.First()}" );
            }
            var response = httpClient.GetAsync( uri )
                .GetAwaiter()
                .GetResult();
            var content = response.Content.ReadAsStringAsync()
                .GetAwaiter()
                .GetResult();
            Console.WriteLine( $"{(int)response.StatusCode} {response.StatusCode}" );
            Console.WriteLine( content );
            httpClient.Dispose();
            httpClientHandler.Dispose();

Solution

  • According to your description, you want to manage Azure cloud service. Azure cloud service is Azure classic resource. So we need to use Azure service management API to manage it. If we want to call the API, we need to do X509 client certificates authentication. For more details, please refer to the document

    The detailed steps are as below

    1. Upload certificate to Azure a. create a certificate

      $cert = New-SelfSignedCertificate -DnsName yourdomain.cloudapp.net -CertStoreLocation "cert:\LocalMachine\My" -KeyLength 2048 -KeySpec "KeyExchange"
      $password = ConvertTo-SecureString -String "your-password" -Force -AsPlainText
      Export-PfxCertificate -Cert $cert -FilePath ".\my-cert-file.pfx" -Password $password
      Export-Certificate -Type CERT -Cert $cert -FilePath .\my-cert-file.cer
      

      b upload .cer file to Azure(Subscriptions -> your subscription -> Management certificates) enter image description here

    2. Code (for example, I list cloud service in my subscription )

     static async Task Main(string[] args)
     {
       var _clientHandler = new HttpClientHandler();
                    _clientHandler.ClientCertificates.Add(GetStoreCertificate("the cert's thumbprint" ));
                    _clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
                    String uri = string.Format("https://management.core.windows.net/{0}/services/hostedservices", "subscription id");
                    using (var _client = new HttpClient(_clientHandler))
                    using (var request = new HttpRequestMessage(HttpMethod.Get, uri)) {
      
                        request.Headers.Add("x-ms-version", "2014-05-01");
                        request.Headers.Add("Accept", "application/xml");
                        //request.Headers.Add("Content-Type", "application/xml");
                        using (HttpResponseMessage httpResponseMessage = await _client.SendAsync(request)) {
                            string xmlString = await httpResponseMessage.Content.ReadAsStringAsync();
                            Console.WriteLine(httpResponseMessage.StatusCode);
                            Console.WriteLine(xmlString);
                        }
    
                    }
    }
    private static X509Certificate2 GetStoreCertificate(string thumbprint)
            {
    
    
                X509Store store = new X509Store("My", StoreLocation.LocalMachine);
                try
                {
                    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
                    X509Certificate2Collection certificates = store.Certificates.Find(
                      X509FindType.FindByThumbprint, thumbprint, false);
                    if (certificates.Count == 1)
                    {
                        return certificates[0];
                    }
                }
                finally
                {
                    store.Close();
                }
    
                throw new ArgumentException(string.Format(
                  "A Certificate with Thumbprint '{0}' could not be located.",
                  thumbprint));
            }
    

    enter image description here