Search code examples
c#certificateazure-notificationhub

Get APNS Certificatefor Azure Notification Hub


I want to create a app so that I can audit all of our 200+ notification hubs. This it to primarily find certificates for APNS that are about to expire.

I can do it just fine in PowerShell, but I cannot get this right in C# as the Fluent SDK does not have a method to the credentials like PowerShell.

var credentials = SdkContext.AzureCredentialsFactory
    .FromServicePrincipal(clientId,
    clientSecret,
    tenantId,
    AzureEnvironment.AzureGlobalCloud);

var azure = Azure
    .Configure()
    .Authenticate(credentials)
    .WithDefaultSubscription();

NotificationHubsManagementClient nhClient = new NotificationHubsManagementClient(credentials);
nhClient.SubscriptionId = subscriptionId;

var nhNamespaces = nhClient.Namespaces.ListAllAsync().Result;

foreach (var nhNamespace in nhNamespaces)
{
    var nhHubs = nhClient.NotificationHubs.ListAsync(resourceGroupName, nhNamespace.Name).Result;

    foreach (var nhHub in nhHubs)
    {
        var hub = nhClient.NotificationHubs.GetAsync(resourceGroupName, nhNamespace.Name, nhHub.Name).Result;

        //THEY ARE ALWAYS NULL AND THERE IS NO METHOD TO GET THE APNS CREDENTIALS
        if (hub.ApnsCredential != null)
        {
            var apnsCred = hub.ApnsCredential;
            Console.WriteLine(apnsCred.ApnsCertificate);
        }
    }
}

In PowerShell I can call:

$pnsCred = Get-AzureRmNotificationHubPNSCredentials -ResourceGroup $resourceGroup -Namespace $hubNamespaceName -NotificationHub $hub.Name

I need a way to get the hub credentials in C#.


MY ALMOST FINAL SOLUTION (NEEDS MORE REFRACTORING AND ERROR HANDLING):

using Microsoft.Azure.Management.NotificationHubs.Fluent;
using Microsoft.Azure.Management.NotificationHubs.Fluent.Models;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Newtonsoft.Json;
using RestSharp;
using System;
using System.Linq;
using System.Configuration;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using mpu.sql;
using mpu.snow;
using mpu.helpers;

namespace mpu.azure
{
    public static class NotificationHubHelper
    {
        private static string tenantId = ConfigurationManager.AppSettings["nhIda:tenantId"];
        private static string clientId = ConfigurationManager.AppSettings["nhIda:clientId"];
        private static string clientSecret = ConfigurationManager.AppSettings["nhIda:clientSecret"];
        private static string resourceGroupName = ConfigurationManager.AppSettings["nhIda:resourceGroupName"];
        private static string subscriptionId = ConfigurationManager.AppSettings["nhIda:subscriptionId"];
        private static DateTime expiryCheck = DateTime.Now.AddDays(-30);

        public static void CheckHubApnsCerts()
        {
            var credentials = SdkContext.AzureCredentialsFactory
                .FromServicePrincipal(clientId,
                clientSecret,
                tenantId,
                AzureEnvironment.AzureGlobalCloud);

            NotificationHubsManagementClient nhClient = new NotificationHubsManagementClient(credentials);
            nhClient.SubscriptionId = subscriptionId;

            var nhNamespaces = nhClient.Namespaces.ListAllAsync().Result;

            nhNamespaces.AsParallel().ForAll(nhNamespace =>
            {
                var nhHubs = nhClient.NotificationHubs.ListAsync(resourceGroupName, nhNamespace.Name).Result;

                nhHubs.AsParallel().ForAll(async (nhHub) =>
                {
                    PnsCredentialResponse pnsCredential = await GetPnsCredential(nhNamespace, nhHub, nhClient.ApiVersion);

                    if (!string.IsNullOrEmpty(pnsCredential?.properties?.apnsCredential?.properties?.apnsCertificate))
                    {
                        var certRawValue = pnsCredential.properties.apnsCredential.properties.apnsCertificate;
                        var certKey = pnsCredential.properties.apnsCredential.properties.certificateKey;

                        var cert = new X509Certificate2(Convert.FromBase64String(certRawValue), certKey, X509KeyStorageFlags.MachineKeySet
                                                  | X509KeyStorageFlags.PersistKeySet
                                                  | X509KeyStorageFlags.Exportable);

                        Console.ForegroundColor = ConsoleColor.Green;
                        if (cert.NotAfter <= expiryCheck)
                        {
                            Console.ForegroundColor = ConsoleColor.Red;

                            try
                            {
                                var certIncident = SqlHelper.GetRecordByCertThumb(cert.Thumbprint);

                                if (certIncident == null)
                                {
                                    var incidentNumber = SnowIncidents.CreateP2Incident($"Notification Hub APNS Certificate Expiring {nhHub.Name}", $"The notification hub APNS certificate for hub {nhHub.Name} is due to expire on {cert.NotAfter}. Please verify in Azure and request a new certificate from the client ASAP.");

                                    if (!string.IsNullOrEmpty(incidentNumber))
                                        SqlHelper.CreateIncidentRecord(cert.Thumbprint, incidentNumber);
                                }
                            }
                            catch
                            {
                                EmailHelper.SendCertExpiryEmailForNotificationHub(nhHub.Name, cert.NotAfter);
                            }
                        }

                        Console.WriteLine($"{nhHub.Name} - {cert.NotAfter} - {cert.Thumbprint}");
                    }
                });
            });
        }

        private static async Task<PnsCredentialResponse> GetPnsCredential(NamespaceResourceInner nhNamespace, NotificationHubResourceInner nhHub, string apiVerion)
        {
            var requestString = $"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.NotificationHubs/namespaces/{nhNamespace.Name}/notificationHubs/{nhHub.Name}/pnsCredentials?api-version={apiVerion}";

            var client = new RestClient(requestString);
            var request = new RestRequest(Method.POST);
            request.AddHeader("Authorization", $"Bearer {await GetAccessToken(tenantId, clientId, clientSecret)}");
            IRestResponse response = client.Execute(request);

            var pnsCredential = JsonConvert.DeserializeObject<PnsCredentialResponse>(response.Content);
            return pnsCredential;
        }

        private static async Task<string> GetAccessToken(string tenantId, string clientId, string clientKey)
        {
            string authContextURL = "https://login.windows.net/" + tenantId;
            var authenticationContext = new AuthenticationContext(authContextURL);
            var credential = new ClientCredential(clientId, clientKey);
            var result = await authenticationContext
                .AcquireTokenAsync("https://management.azure.com/", credential);

            if (result == null)
                throw new InvalidOperationException("Failed to obtain the JWT token for management.azure.com");

            return result.AccessToken;
        }
    }

    public class PnsCredentialResponse
    {
        public string id { get; set; }
        public string name { get; set; }
        public string type { get; set; }
        public object location { get; set; }
        public object tags { get; set; }
        public Properties properties { get; set; }
    }

    public class Properties
    {
        public Apnscredential apnsCredential { get; set; }
    }

    public class Apnscredential
    {
        [JsonProperty("properties")]
        public ApnsProperties properties { get; set; }
    }

    public class ApnsProperties
    {
        public string endpoint { get; set; }
        public string apnsCertificate { get; set; }
        public string certificateKey { get; set; }
        public string thumbprint { get; set; }
    }
}

Solution

  • The Azure PowerShell modules will be built on top of the Management SDK. The most up-to-date version can be found on NuGet. The source code for these SDK can be found on GitHub. If memory serves, I believe the NamespaceOperations.GetWithHttpMessagesAsync method is what you're looking for.

    It's worth noting that the management functionality was previously in v1 of the NotificationHubs SDK itself. It was taken out in v2 to prevent confusion about having multiple means of doing the same thing. However, popular demand is asserting that the management functionality gets brought back, so that there is a single package to work with all things Notification Hubs. At the time of authoring, those changes are in review.