/// <summary> /// Creates the vault management instance based on provided Vaults Config dictionary /// </summary> /// <param name="vaultsConfig">Vaults Config dictionary</param> /// <param name="accessType">ReadOnly or ReadWrite</param> /// <param name="vaultNames">Single or Dual</param> public Vault(VaultsConfig vaultsConfig, VaultAccessTypeEnum accessType, params string[] vaultNames) { Guard.ArgumentNotNull(vaultsConfig, nameof(vaultsConfig)); Guard.ArgumentCollectionNotEmpty(vaultNames, nameof(vaultNames)); VaultsConfig = vaultsConfig; VaultNames = (from v in vaultNames where !string.IsNullOrEmpty(v) select v).ToArray(); switch (VaultNames.Length) { case 1: _keyVaultClients = new KeyVaultClientEx[1] { CreateKeyVaultClientEx(accessType, VaultNames[0]), }; break; case 2: string primaryVaultName = VaultNames[0]; string secondaryVaultName = VaultNames[1]; if (0 == string.Compare(primaryVaultName, secondaryVaultName, true)) { throw new ArgumentException($"Primary vault name {primaryVaultName} is equal to secondary vault name {secondaryVaultName}"); } _keyVaultClients = new KeyVaultClientEx[2] { CreateKeyVaultClientEx(accessType, primaryVaultName), CreateKeyVaultClientEx(accessType, secondaryVaultName), }; break; default: throw new ArgumentException($"Vault names length must be 1 or 2 only", nameof(VaultNames)); } }
private KeyVaultClientEx CreateKeyVaultClientEx(VaultAccessTypeEnum accessType, string vaultName) => new KeyVaultClientEx(vaultName, (authority, resource, scope) => { lock (Lock) { Utils.GuardVaultName(vaultName); if (false == VaultsConfig.ContainsKey(vaultName)) { throw new KeyNotFoundException($"{vaultName} is not found in {VaultsConfigFile}"); } VaultAccessType vat = VaultsConfig[vaultName]; VaultAccess[] vas = (accessType == VaultAccessTypeEnum.ReadOnly) ? vat.ReadOnly : vat.ReadWrite; // Order possible VaultAccess options by Order property IEnumerable <VaultAccess> vaSorted = from va in vas orderby va.Order select va; // In case VaultAccessUserInteractive is in the list, we will use our FileTokenCache with provided domainHint, otherwise use MemoryTokenCache string domainHint = (from va in vaSorted where va is VaultAccessUserInteractive select(VaultAccessUserInteractive) va).FirstOrDefault()?.DomainHint; string userAliasType = (from va in vaSorted where va is VaultAccessUserInteractive select(VaultAccessUserInteractive) va).FirstOrDefault()?.UserAliasType; // Token cache name is unique per login credentials as it uses alias type or env user name and domain hint. string tokenCacheName = $"{(userAliasType ?? Environment.UserName)}@{domainHint ?? "microsoft.com"}"; // If either user alias or domain hint are empty, cache in memory instead. var authenticationContext = new AuthenticationContext(authority, string.IsNullOrEmpty(domainHint) && string.IsNullOrEmpty(userAliasType) ? new MemoryTokenCache() : (TokenCache) new FileTokenCache(tokenCacheName)); Queue <Exception> exceptions = new Queue <Exception>(); string vaultAccessTypes = ""; foreach (VaultAccess va in vaSorted) { try { // If user alias type is different from environment, force login prompt, otherwise silently login var authResult = va.AcquireToken(authenticationContext, resource, userAliasType == Environment.UserName ? Environment.UserName:""); AuthenticatedUserName = authResult.UserInfo?.DisplayableId ?? $"{Environment.UserDomainName}\\{Environment.UserName}"; return(Task.FromResult(authResult.AccessToken)); } catch (Exception e) { vaultAccessTypes += $" {va}"; exceptions.Enqueue(e); } } throw new VaultAccessException($"Failed to get access to {vaultName} with all possible vault access type(s){vaultAccessTypes}", exceptions.ToArray()); } });
private KeyVaultClientEx CreateKeyVaultClientEx(VaultAccessTypeEnum accessType, string vaultName) => new KeyVaultClientEx(vaultName, (authority, resource, scope) => { Utils.GuardVaultName(vaultName); if (false == VaultsConfig.ContainsKey(vaultName)) { throw new KeyNotFoundException($"{vaultName} is not found in {VaultsConfigFile}"); } VaultAccessType vat = VaultsConfig[vaultName]; VaultAccess[] vas = (accessType == VaultAccessTypeEnum.ReadOnly) ? vat.ReadOnly : vat.ReadWrite; // Order possible VaultAccess options by Order property IEnumerable <VaultAccess> vaSorted = from va in vas orderby va.Order select va; // In case VaultAccessUserInteractive is in the list, we will use our FileTokenCache with provided domainHint, otherwise use MemoryTokenCache string domainHint = (from va in vaSorted where va is VaultAccessUserInteractive select(VaultAccessUserInteractive) va).FirstOrDefault()?.DomainHint; var authenticationContext = new AuthenticationContext(authority, string.IsNullOrEmpty(domainHint) ? new MemoryTokenCache() : (TokenCache) new FileTokenCache(domainHint)); Queue <Exception> exceptions = new Queue <Exception>(); string vaultAccessTypes = ""; foreach (VaultAccess va in vaSorted) { try { var authResult = va.AcquireToken(authenticationContext, resource); AuthenticatedUserName = authResult.UserInfo?.DisplayableId ?? $"{Environment.UserDomainName}\\{Environment.UserName}"; return(Task.FromResult(authResult.AccessToken)); } catch (Exception e) { vaultAccessTypes += $" {va}"; exceptions.Enqueue(e); } } throw new VaultAccessException($"Failed to get access to {vaultName} with all possible vault access type(s){vaultAccessTypes}", exceptions.ToArray()); });
/// <summary> /// Dual (primary and secondary) vault management constructor /// </summary> /// <param name="accessType">ReadOnly or ReadWrite</param> /// <param name="primaryVaultName">Primary vault name</param> /// <param name="secondaryVaultName">Secodnary vault name</param> public Vault(VaultAccessTypeEnum accessType, string primaryVaultName, string secondaryVaultName) : this(accessType, new string[] { primaryVaultName, secondaryVaultName }) { }
/// <summary> /// Single (primary) or Dual (primary and secondary) vault management constructor /// </summary> /// <param name="accessType">ReadOnly or ReadWrite</param> /// <param name="vaultName">Vault name</param> public Vault(VaultAccessTypeEnum accessType, string vaultName) : this(accessType, new string[] { vaultName }) { }
/// <summary> /// Single (primary) vault management constructor /// </summary> /// <param name="accessType">ReadOnly or ReadWrite</param> /// <param name="vaultNames">Single or pair</param> public Vault(VaultAccessTypeEnum accessType, params string[] vaultNames) : this(string.Empty, accessType, vaultNames) { }
/// <summary> /// Load specified Vaults.json configuration file and creates the vault management instance /// </summary> /// <param name="vaultsConfigFile"> /// Optional path to Vaults.json file, if NULL or empty default Vaults.json will be used, in such case Vaults.json location will be resolved in the following order: /// 1. Side-by-side with the current process location /// 2. Side-by-side with the current (executing) assembly /// </param> /// <param name="accessType">ReadOnly or ReadWrite</param> /// <param name="vaultNames">Single or Dual</param> public Vault(string vaultsConfigFile, VaultAccessTypeEnum accessType, params string[] vaultNames) : this(DeserializeVaultsConfigFromFile(ref vaultsConfigFile), accessType, vaultNames) { VaultsConfigFile = vaultsConfigFile; }