Creating An Azure API To Generate Random SSH Keys

In an earlier article we described the thought process and architecture for setting up an API Store that will serve as the shared service platform providing developers access to most common and frequently required functionalities being exposed in the form of consumable APIs. The API store needs to continue to evolve and provide more and more implementations to become effective and help reduce the turnaround times for new custom applications. 
 
Creating An Azure API To Generate Random SSH Keys
 
While working for one of our recent engagements, there became a need for us to manage the admin keys for Linux VMs as part of ongoing infrastructure support. These keys were being used by the admins on the client side and by the support team to do the routine maintenance and admin tasks. In the current setups, these keys were being manually generated and rotated based on client requests. In an environment with a small footprint this may be a feasible task however with bigger footprints, this becomes an overhead for the operations team to handle and address either on an as-needed basis or on a scheduled basis based on the client policies. Needless to say a certain level of aquaintance and understanding of Linux systems is needed to complete the admin tasks. 
 
Being a pro Microsoft and Windows team, this presented a unique problem to tackle which is what drove us to find a tangible solution to the continous problem. We went the automation route and decided to implement a solution which takes care of key generation and rotation  which involved creating APIs for  a) generation of SSH Keys and b) storing and fetching them from key vaults and then scheduling a rotation using Azure Automation. In this article, we will  focus on one part of this solution which is creating the API to generate the SSH keys.
 
With that context, let's get started with writing few lines of code for our API. This functionality is going to rely on the SSHKeyGenerator library.
 
This provides us with a native .NET and .NET Core library for creating SSH RSA keys suitable for use with SSH clients and Git+SSH authentication. It generates both kinds of keys – private and public, and these keys can be used for any Linux based Azure VM.
 
Once we have the package installed and available in our project, we will create a new function class file GenerateSSHKeys.cs and add a GET function called GenerateNewSSHKeys. This function will expect a "VMName" (virtual machine name) query parameter to be passed which will be used as the input for generating the SSH Public key. This new function is also using the custom log analytics API that we created in our earlier article for logging the actions and errors in the finally block. We have also defined a class entity for capturing the generated SSH Public and Private Keys. 
 
GenerateSSHKeys.cs 
  1. using System;  
  2. using System.IO;  
  3. using System.Threading.Tasks;  
  4. using Microsoft.Azure.WebJobs;  
  5. using Microsoft.Azure.WebJobs.Extensions.Http;  
  6. using Microsoft.Extensions.Logging;  
  7. using System.Net.Http;  
  8. using Newtonsoft.Json.Linq;  
  9. using Newtonsoft.Json;  
  10. using Microsoft.AspNetCore.Mvc;  
  11. using Microsoft.AspNetCore.Http;  
  12. using Reusable.Functions;  
  13.   
  14. namespace Reusable  
  15. {  
  16.     /// <summary>  
  17.     /// This function is to generate both kind of SSH keys - Private and Public   
  18.     /// </summary>  
  19.     ///   
  20.     public static class GenerateSSHKey  
  21.     {  
  22.         [FunctionName("GenerateNewSSHKeys")]  
  23.         public static async Task<IActionResult> Run(  
  24.             [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,  
  25.             ILogger log)  
  26.         {  
  27.             string passwordGenerated = string.Empty;  
  28.              
  29.             #region dynamic jobject creation for log analytics logs  
  30.             dynamic jObject = new JObject();  
  31.             jObject.LogFileName = ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.logName);  
  32.             jObject.AutomationName = "Reusable";  
  33.             jObject.ModuleName = "GenerateSSHKey";  
  34.             dynamic logJObject = new JObject();  
  35.             #endregion  
  36.   
  37.             log.LogInformation("GenerateSSHKey Function is called");  
  38.             logJObject.LogInformation = "GenerateSSHKey Function is called";  
  39.               
  40.             try  
  41.             {  
  42.                 int keyBits = 2048;  
  43.                 //get VM Name for which key needs to be generated  
  44.                 string keyComment = req.Query["VMName"];  
  45.                 if(!String.IsNullOrEmpty(keyComment))  
  46.                 {  
  47.                     string requestBody = await new StreamReader(req.Body).ReadToEndAsync();  
  48.                     dynamic data = JsonConvert.DeserializeObject(requestBody);  
  49.                     keyComment = keyComment ?? data?.VMName;  
  50.                     keyComment = keyComment + "-" + DateTime.Now.ToShortDateString();  
  51.   
  52.                     //Generate new SSH Keys  
  53.                     var generator = new SshKeyGenerator.SshKeyGenerator(keyBits);  
  54.   
  55.                     if (generator != null)  
  56.                     {  
  57.                         SSHKeyPair generatedPair = new SSHKeyPair();  
  58.                         generatedPair.SSHPrivateKey = generator.ToPrivateKey();    
  59.                         generatedPair.SSHPublicKey= generator.ToRfcPublicKey(keyComment);  
  60.                         log.LogInformation("Keys have been generated.");  
  61.                         logJObject.LogInformation += "\n Keys have been generated.";  
  62.                         return new OkObjectResult(JsonConvert.SerializeObject(generatedPair));  
  63.                     }  
  64.                     else  
  65.                     {  
  66.                        log.LogInformation("Exception has been occured in GenerateSSHKey. Please check Function logs under Monitor.");  
  67.                        logJObject.LogInformation += "\n Exception has been occured in GenerateSSHKey. Please check Function logs under Monitor.";  
  68.                        return new NotFoundObjectResult("error result");  
  69.                     }  
  70.                 }  
  71.                 else  
  72.                 {  
  73.                     return new OkObjectResult(JsonConvert.SerializeObject("Please provide a VM Name for which SSH Keys are to be generated."));                  }  
  74.             }  
  75.             catch(Exception ex)  
  76.             {  
  77.                 log.LogInformation($"GenerateSSHKey got Exception Time: { DateTime.Now} Exception{ ex.Message}");  
  78.                 logJObject.LogInformation += $"\n GenerateSSHKey got Exception Time: { DateTime.Now} Exception{ ex.Message}";  
  79.                 return new NotFoundObjectResult("");  
  80.             }  
  81.             #region finally block for pushing logs into log analytics workspace  
  82.             finally  
  83.             {  
  84.                 using(var client = new HttpClient())  
  85.                 {  
  86.                     string logJson = logJObject.ToString(Newtonsoft.Json.Formatting.None);  
  87.                     jObject.LogData = logJson;  
  88.                     string myJson = jObject.ToString(Newtonsoft.Json.Formatting.None);  
  89.                     //Invoking PushLogsToLogAnalytics API for logging in Log Analytics  
  90.                     client.DefaultRequestHeaders.Add(ConstantsHelper.ocp_Apim_Subscription_Key, ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.ocp_Apim_Subscription_Key));  
  91.                     var response = await client.PostAsync(ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.PushLogsToLogAnalyticsAPI), new StringContent(myJson, System.Text.Encoding.UTF8, "application/json"));  
  92.                     if (response.StatusCode == System.Net.HttpStatusCode.OK)  
  93.                     {  
  94.                           
  95.                         log.LogInformation("Logging is completed successfully with status code : " +response.StatusCode);  
  96.                     }  
  97.                     else  
  98.                     {  
  99.                         log.LogInformation("Logging is failed with status code : " + response.StatusCode);  
  100.                     }  
  101.                 }  
  102.             }  
  103.             #endregion  
  104.         }  
  105.     }  
  106.   
  107.     public class SSHKeyPair  
  108.     {  
  109.         public string SSHPrivateKey {get;set;}  
  110.         public string SSHPublicKey {get;set;}  
  111.     }  
  112. }  
That's it! The function code is ready and can now be published to the Function App that is created in the Azure Subscription. Once the new function is published and available, we will need to add this to the API Management instance that we have created. The steps to publish and add function to the API Management instance are documented in this article. The actual endpoint to end users will be made available through Azure API Management once it is configured but for our test, let's run it directly from the Azure Portal using the Azure Functions Test Run feature.
 
Azure Portal Navigation: Function App => Functions => GenerateNewSSHKeys => Code + Test => Test/Run
 
Creating An Azure API To Generate Random SSH Keys
 
Creating An Azure API To Generate Random SSH Keys
 
So this simple API implementation can now be integrated with any automation setup where SSH key generation is to be done dynamically. This simple implementation is really useful and can be utilized in multiple automations like key rotations, generation of new random keys during VM provisioining etc. Being an API based implementation means that it can pretty much be invoked in any language and in any functionality. It also eliminates the need for application developers and support engineers to have contextualized Linux knowledge to generate and rotate keys.
 
The complete code for this API is available at our GitHub Repository along with a few other APIs that can be generically used in any API Store implementations. 
 
Happy Coding!