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.

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
- using System;
- using System.IO;
- using System.Threading.Tasks;
- using Microsoft.Azure.WebJobs;
- using Microsoft.Azure.WebJobs.Extensions.Http;
- using Microsoft.Extensions.Logging;
- using System.Net.Http;
- using Newtonsoft.Json.Linq;
- using Newtonsoft.Json;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.AspNetCore.Http;
- using Reusable.Functions;
- namespace Reusable
- {
- /// <summary>
- /// This function is to generate both kind of SSH keys - Private and Public
- /// </summary>
- ///
- public static class GenerateSSHKey
- {
- [FunctionName("GenerateNewSSHKeys")]
- public static async Task<IActionResult> Run(
- [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
- ILogger log)
- {
- string passwordGenerated = string.Empty;
- #region dynamic jobject creation for log analytics logs
- dynamic jObject = new JObject();
- jObject.LogFileName = ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.logName);
- jObject.AutomationName = "Reusable";
- jObject.ModuleName = "GenerateSSHKey";
- dynamic logJObject = new JObject();
- #endregion
- log.LogInformation("GenerateSSHKey Function is called");
- logJObject.LogInformation = "GenerateSSHKey Function is called";
- try
- {
- int keyBits = 2048;
- //get VM Name for which key needs to be generated
- string keyComment = req.Query["VMName"];
- if(!String.IsNullOrEmpty(keyComment))
- {
- string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
- dynamic data = JsonConvert.DeserializeObject(requestBody);
- keyComment = keyComment ?? data?.VMName;
- keyComment = keyComment + "-" + DateTime.Now.ToShortDateString();
- //Generate new SSH Keys
- var generator = new SshKeyGenerator.SshKeyGenerator(keyBits);
- if (generator != null)
- {
- SSHKeyPair generatedPair = new SSHKeyPair();
- generatedPair.SSHPrivateKey = generator.ToPrivateKey();
- generatedPair.SSHPublicKey= generator.ToRfcPublicKey(keyComment);
- log.LogInformation("Keys have been generated.");
- logJObject.LogInformation += "\n Keys have been generated.";
- return new OkObjectResult(JsonConvert.SerializeObject(generatedPair));
- }
- else
- {
- log.LogInformation("Exception has been occured in GenerateSSHKey. Please check Function logs under Monitor.");
- logJObject.LogInformation += "\n Exception has been occured in GenerateSSHKey. Please check Function logs under Monitor.";
- return new NotFoundObjectResult("error result");
- }
- }
- else
- {
- return new OkObjectResult(JsonConvert.SerializeObject("Please provide a VM Name for which SSH Keys are to be generated.")); }
- }
- catch(Exception ex)
- {
- log.LogInformation($"GenerateSSHKey got Exception Time: { DateTime.Now} Exception{ ex.Message}");
- logJObject.LogInformation += $"\n GenerateSSHKey got Exception Time: { DateTime.Now} Exception{ ex.Message}";
- return new NotFoundObjectResult("");
- }
- #region finally block for pushing logs into log analytics workspace
- finally
- {
- using(var client = new HttpClient())
- {
- string logJson = logJObject.ToString(Newtonsoft.Json.Formatting.None);
- jObject.LogData = logJson;
- string myJson = jObject.ToString(Newtonsoft.Json.Formatting.None);
- //Invoking PushLogsToLogAnalytics API for logging in Log Analytics
- client.DefaultRequestHeaders.Add(ConstantsHelper.ocp_Apim_Subscription_Key, ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.ocp_Apim_Subscription_Key));
- var response = await client.PostAsync(ConstantsHelper.GetEnvironmentVariable(ConstantsHelper.PushLogsToLogAnalyticsAPI), new StringContent(myJson, System.Text.Encoding.UTF8, "application/json"));
- if (response.StatusCode == System.Net.HttpStatusCode.OK)
- {
- log.LogInformation("Logging is completed successfully with status code : " +response.StatusCode);
- }
- else
- {
- log.LogInformation("Logging is failed with status code : " + response.StatusCode);
- }
- }
- }
- #endregion
- }
- }
- public class SSHKeyPair
- {
- public string SSHPrivateKey {get;set;}
- public string SSHPublicKey {get;set;}
- }
- }
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


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!

Join the conversation! Your thoughts help the community grow.