/// <summary> /// This method verifies that all of the required inputs exist within the Json file. /// </summary> /// <param name="vaultList">The KeyVault information obtained from MasterConfig.json file</param> /// <param name="configVaults">The Json object formed from parsing the MasterConfig.json file</param> public void checkJsonFields(JsonInput vaultList, JObject configVaults) { List <string> missingInputs = new List <string>(); int numValid = 0; if (vaultList.Resources != null) { ++numValid; } else { missingInputs.Add("Resources"); } int numMissing = missingInputs.Count(); if (missingInputs.Count() == 0 && configVaults.Children().Count() != numValid) { throw new Exception($"Invalid fields in Json were defined. Only valid field is 'Resources'."); } else if (missingInputs.Count() != 0 && configVaults.Children().Count() != numValid) { throw new Exception($"Missing {string.Join(" ,", missingInputs)} in Json. Invalid fields were defined; " + $"Only valid field is 'Resources'."); } else if (missingInputs.Count() > 0) { throw new Exception($"Missing {string.Join(" ,", missingInputs)} in Json."); } }
/// <summary> /// This method verifies that reading invalid Resource Fields are handled. /// </summary> public void TestCheckMissingResourceFieldsInvalid() { AccessPoliciesToYaml ap = new AccessPoliciesToYaml(true); string masterConfig = System.IO.File.ReadAllText("../../../Input/MasterConfig.json"); JObject configVaults = JObject.Parse(masterConfig); JsonInput missingResourceGroupName = createExpectedJson(new List <Resource>()); missingResourceGroupName.Resources[0].ResourceGroups[0].ResourceGroupName = null; JsonInput missingSubscriptionId = createExpectedJson(new List <Resource>()); missingSubscriptionId.Resources[1].SubscriptionId = null; List <Testing <JsonInput> > negativeTestMissingResourceFields = new List <Testing <JsonInput> >() { new Testing <JsonInput>(missingResourceGroupName, "Missing 'ResourceGroupName' for ResourceGroup. Invalid fields were defined; valid fields are 'ResourceGroupName' and 'KeyVaults'."), new Testing <JsonInput>(missingSubscriptionId, "Missing 'SubscriptionId' for Resource. Invalid fields were defined; valid fields are 'SubscriptionId' and 'ResourceGroups'.") }; foreach (Testing <JsonInput> testCase in negativeTestMissingResourceFields) { try { ap.checkMissingResourceFields(testCase.testObject, configVaults); Assert.Fail(); } catch (Exception e) { Assert.AreEqual(testCase.error, e.Message); } } }
public static List <KeyVaultProperties> runProgram(string[] args, bool testing) { AccessPoliciesToYaml ap = new AccessPoliciesToYaml(testing); UpdatePoliciesFromYaml up = new UpdatePoliciesFromYaml(testing); Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine("Refer to 'Log.log' for more details should an error be thrown.\n"); Console.ResetColor(); Console.WriteLine("Reading input files..."); up.verifyFileExtensions(args); JsonInput vaultList = ap.readJsonFile(args[0]); Console.WriteLine("Finished!"); Console.WriteLine("Grabbing secrets..."); Dictionary <string, string> secrets = ap.getSecrets(); Console.WriteLine("Finished!"); Console.WriteLine("Creating KeyVaultManagementClient, GraphServiceClient, and AzureClient..."); KeyVaultManagementClient kvmClient = ap.createKVMClient(secrets); GraphServiceClient graphClient = ap.createGraphClient(secrets); IAuthenticated azureClient = ap.createAzureClient(secrets); Console.WriteLine("Finished!");; Console.WriteLine("Checking access and retrieving key vaults..."); ap.checkAccess(vaultList, azureClient); List <KeyVaultProperties> vaultsRetrieved = ap.getVaults(vaultList, kvmClient, graphClient); Console.WriteLine("Finished!"); Console.WriteLine("Reading yaml file..."); List <KeyVaultProperties> yamlVaults = up.deserializeYaml(args[1]); Console.WriteLine("Finished!"); Console.WriteLine("Updating key vaults..."); List <KeyVaultProperties> deletedPolicies = up.updateVaults(yamlVaults, vaultsRetrieved, kvmClient, secrets, graphClient); Console.WriteLine("Finished!"); Console.WriteLine("Generating DeletedPolicies yaml..."); up.convertToYaml(deletedPolicies, args[2]); Console.WriteLine("Finished!"); if (testing) { return(up.Changed); } return(null); }
/// <summary> /// This method verifies that all of the required inputs exist for each Resource object. /// </summary> /// <param name="vaultList">The KeyVault information obtained from MasterConfig.json file</param> /// <param name="configVaults">The Json object formed from parsing the MasterConfig.json file</param> public void checkMissingResourceFields(JsonInput vaultList, JObject configVaults) { JEnumerable <JToken> resourceList = configVaults.SelectToken($".Resources").Children(); int i = 0; foreach (Resource res in vaultList.Resources) { JToken jres = resourceList.ElementAt(i); if (res.SubscriptionId != null && res.ResourceGroups.Count() == 0 && jres.Children().Count() > 1) { throw new Exception($"Invalid fields for Resource with SubscriptionId '{res.SubscriptionId}' were defined. Valid fields are 'SubscriptionId' and 'ResourceGroups'."); } else if (res.SubscriptionId == null && jres.Children().Count() > 0) { throw new Exception($"Missing 'SubscriptionId' for Resource. Invalid fields were defined; valid fields are 'SubscriptionId' and 'ResourceGroups'."); } else if (res.SubscriptionId != null && res.ResourceGroups.Count() != 0) { if (jres.Children().Count() > 2) { throw new Exception($"Invalid fields other than 'SubscriptionId' and 'ResourceGroups' were defined for Resource with SubscriptionId '{res.SubscriptionId}'."); } int j = 0; foreach (ResourceGroup resGroup in res.ResourceGroups) { JEnumerable <JToken> groupList = jres.SelectToken($".ResourceGroups").Children(); JToken jresGroup = groupList.ElementAt(j); if (resGroup.ResourceGroupName != null && resGroup.KeyVaults.Count() == 0 && jresGroup.Children().Count() > 1) { throw new Exception($"Invalid fields for ResourceGroup with ResourceGroupName '{resGroup.ResourceGroupName}' were defined. " + $"Valid fields are 'ResourceGroupName' and 'KeyVaults'."); } else if (resGroup.ResourceGroupName == null && jresGroup.Children().Count() > 0) { throw new Exception("Missing 'ResourceGroupName' for ResourceGroup. Invalid fields were defined; valid fields are 'ResourceGroupName' and 'KeyVaults'."); } else if (resGroup.ResourceGroupName != null && resGroup.KeyVaults.Count() != 0 && jresGroup.Children().Count() > 2) { throw new Exception($"Invalid fields other than 'ResourceGroupName' and 'KeyVaults' were defined for ResourceGroup " + $"with ResourceGroupName '{resGroup.ResourceGroupName}'."); } ++j; } } ++i; } }
/// <summary> /// This method reads in and deserializes the json input file. /// </summary> /// <param name="jsonDirectory">The json file path</param> /// <returns>A JsonInput object that stores the json input data</returns> public JsonInput readJsonFile(string jsonDirectory) { log.Info("Reading in Json file...."); try { string masterConfig = System.IO.File.ReadAllText(jsonDirectory); JsonInput vaultList = JsonConvert.DeserializeObject <JsonInput>(masterConfig); JObject configVaults = JObject.Parse(masterConfig); checkJsonFields(vaultList, configVaults); checkMissingResourceFields(vaultList, configVaults); log.Info("Json file read!"); return(vaultList); } catch (Exception e) { log.Error("DeserializationFail", e); log.Debug("Refer to https://github.com/microsoft/Managing-RBAC-in-Azure/blob/master/Config/MasterConfigExample.json for questions on formatting and inputs. Ensure that you have all the required fields with valid values, then try again."); Exit(e.Message); return(null); } }
/// <summary> /// This method creates an expected json that is used for testing purposes. /// </summary> /// <param name="resources"> List of Resources </param> /// <returns>The expected deserialized JsonInput</returns> private JsonInput createExpectedJson(List <Resource> resources) { var expectedJson = new JsonInput(); expectedJson.Resources = resources; if (resources != null) { var res1 = new Resource(); res1.SubscriptionId = "sample1"; var g1 = new ResourceGroup(); g1.ResourceGroupName = "group a"; g1.KeyVaults.Add("VaultA"); var g2 = new ResourceGroup(); g2.ResourceGroupName = "group b"; g2.KeyVaults.Add("VaultB"); res1.ResourceGroups.Add(g1); res1.ResourceGroups.Add(g2); expectedJson.Resources.Add(res1); var res2 = new Resource(); res2.SubscriptionId = "sample2"; expectedJson.Resources.Add(res2); var res3 = new Resource(); res3.SubscriptionId = "sample3"; g1 = new ResourceGroup(); g1.ResourceGroupName = "RBACTest3"; g1.KeyVaults.Add("RBACTestVault1"); g1.KeyVaults.Add("RBACTestVault2"); res3.ResourceGroups.Add(g1); expectedJson.Resources.Add(res3); } return(expectedJson); }
/// <summary> /// This method reads in a Json config file and converts it into a serialized list of KeyVaults that are displayed in a Yaml file. /// </summary> public static void Main(string[] args) { AccessPoliciesToYaml ap = new AccessPoliciesToYaml(false); Console.ForegroundColor = ConsoleColor.DarkGreen; Console.WriteLine("Refer to 'Log.log' for more details should an error be thrown.\n"); Console.ResetColor(); Console.WriteLine("Reading input file..."); ap.verifyFileExtensions(args); JsonInput vaultList = ap.readJsonFile(args[0]); Console.WriteLine("Finished!"); Console.WriteLine("Grabbing secrets..."); Dictionary <string, string> secrets = ap.getSecrets(); Console.WriteLine("Finished!"); Console.WriteLine("Creating KeyVaultManagementClient, GraphServiceClient, and AzureClient..."); KeyVaultManagementClient kvmClient = ap.createKVMClient(secrets); GraphServiceClient graphClient = ap.createGraphClient(secrets); IAuthenticated azureClient = ap.createAzureClient(secrets); Console.WriteLine("Finished!");; Console.WriteLine("Checking access and retrieving key vaults..."); ap.checkAccess(vaultList, azureClient); List <KeyVaultProperties> vaultsRetrieved = ap.getVaults(vaultList, kvmClient, graphClient); Console.WriteLine("Finished!"); Console.WriteLine("Generating YAML output..."); ap.convertToYaml(vaultsRetrieved, args[1]); Console.WriteLine("Finished!"); }
/// <summary> /// This method retrieves each of the KeyVaults specified in "vaultList". /// </summary> /// <param name="vaultList">The data obtained from deserializing json file</param> /// <param name="kvmClient">The KeyVaultManagementClient containing Vaults</param> /// <param name="graphClient">The Microsoft GraphServiceClient for obtaining display names</param> /// <returns>The list of KeyVaultProperties containing the properties of each KeyVault</returns> public List <KeyVaultProperties> getVaults(JsonInput vaultList, KeyVaultManagementClient kvmClient, GraphServiceClient graphClient) { log.Info("Getting Vaults..."); List <Vault> vaultsRetrieved = new List <Vault>(); foreach (Resource res in vaultList.Resources) { log.Info($"Entering SubscriptionID: {res.SubscriptionId}"); // Associates the client with the subscription kvmClient.SubscriptionId = res.SubscriptionId; // Retrieves all KeyVaults at the Subscription scope if (res.ResourceGroups.Count == 0) { vaultsRetrieved = getVaultsAllPages(kvmClient, vaultsRetrieved); } else { bool notFound = false; foreach (ResourceGroup resGroup in res.ResourceGroups) { log.Info($"Entering ResourceGroup: {resGroup.ResourceGroupName}"); // If the Subscription is not found, then do not continue looking for vaults in this subscription if (notFound) { break; } // Retrieves all KeyVaults at the ResourceGroup scope if (resGroup.KeyVaults.Count == 0) { vaultsRetrieved = getVaultsAllPages(kvmClient, vaultsRetrieved, resGroup.ResourceGroupName); } // Retrieves all KeyVaults at the Resource scope else { foreach (string vaultName in resGroup.KeyVaults) { log.Info($"Entering VaultName: {vaultName}"); try { vaultsRetrieved.Add(kvmClient.Vaults.Get(resGroup.ResourceGroupName, vaultName)); } catch (CloudException e) { log.Error(e.Message); ConsoleError(e.Message); if (e.Body.Code == "SubscriptionNotFound") { notFound = true; break; } } } } } } } List <KeyVaultProperties> keyVaultsRetrieved = new List <KeyVaultProperties>(); foreach (Vault curVault in vaultsRetrieved) { keyVaultsRetrieved.Add(new KeyVaultProperties(curVault, graphClient)); } log.Info("Vaults retrieved!"); return(keyVaultsRetrieved); }
/// <summary> /// This method verifies that the Contributor permission has been granted on sufficient scopes to retrieve the key vaults. /// </summary> /// <param name="vaultList">The data obtained from deserializing json file</param> /// <param name="azureClient">The IAzure client used to access role assignments</param> public void checkAccess(JsonInput vaultList, Microsoft.Azure.Management.Fluent.Azure.IAuthenticated azureClient) { log.Info("Verifying access to Vaults..."); List <string> accessNeeded = new List <string>(); IRoleAssignments accessControl = azureClient.RoleAssignments; foreach (Resource res in vaultList.Resources) { try { string subsPath = Constants.SUBS_PATH + res.SubscriptionId; var roleAssignments = accessControl.ListByScope(subsPath).ToLookup(r => r.Inner.Scope); var subsAccess = roleAssignments[subsPath].Count(); if (subsAccess == 0) { // At Subscription scope if (res.ResourceGroups.Count == 0) { accessNeeded.Add(subsPath); } else { foreach (ResourceGroup resGroup in res.ResourceGroups) { string resGroupPath = subsPath + Constants.RESGROUP_PATH + resGroup.ResourceGroupName; var resGroupAccess = roleAssignments[resGroupPath].Count(); if (resGroupAccess == 0) { // At ResourceGroup scope if (resGroup.KeyVaults.Count == 0) { accessNeeded.Add(subsPath); } else { // At Vault scope foreach (string vaultName in resGroup.KeyVaults) { string vaultPath = resGroupPath + Constants.VAULT_PATH + vaultName; var vaultAccess = roleAssignments[vaultPath].Count(); if (vaultAccess == 0) { accessNeeded.Add(vaultPath); } } } } } } } } catch (CloudException e) { log.Error("SubscriptionNotFound"); log.Debug($"{e.Message}. Please verify that your SubscriptionId is valid."); Exit(e.Message); } } if (accessNeeded.Count() != 0) { log.Error("AuthorizationFail"); log.Debug($"Contributor access is needed on the following scope(s): \n{string.Join("\n", accessNeeded)}. \nEnsure that your ResourceGroup and KeyVault names are spelled correctly " + $"before proceeding. Note that if you are retrieving specific KeyVaults, your AAD must be granted access at either the KeyVault, ResourceGroup, Subscription level. " + $"If you are retrieving all of the KeyVaults from a ResourceGroup, your AAD must be granted access at either the ResourceGroup or Subscription level. " + $"If you are retrieving all of the KeyVaults from a SubscriptionId, your AAD must be granted access at the Subscription level. " + $"Refer to the 'Granting Access to the AAD Application' section for more information on granting this access: https://github.com/microsoft/Managing-RBAC-in-Azure/blob/master/README.md"); Exit($"Contributor access is needed on the following scope(s): \n{string.Join("\n", accessNeeded)}"); } log.Info("Access verified!"); }