-
Notifications
You must be signed in to change notification settings - Fork 3.2k
App Configuration Provider - Key Vault Refresh #41882
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 44 commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
89c195d
Sync refresh changes
mrm9084 961efda
Key Vault Refresh
mrm9084 be0ffa7
adding tests and fixing sync refresh
mrm9084 1b0f554
Updating Async
mrm9084 5f54837
Fixed Async Tests
mrm9084 d5e8b87
Updated tests and change log
mrm9084 3f838d7
Apply suggestions from code review
mrm9084 8af8c0e
Merge branch 'main' into KeyVaultRefresh
mrm9084 251b825
Fixing merge issue
mrm9084 382dbba
Updating comments
mrm9084 00985a1
Merge remote-tracking branch 'upstream/main' into KeyVaultRefresh
mrm9084 ef6620a
Merge branch 'main' into KeyVaultRefresh
mrm9084 6d7a8f8
Updating secret refresh
mrm9084 d4a8d71
Merge branch 'KeyVaultRefresh' of https://github.com/mrm9084/azure-sd…
mrm9084 7ccb2a0
Update _azureappconfigurationproviderasync.py
mrm9084 2663cb3
Fixing Optional Endpoint
mrm9084 12fbdda
fix mypy issue
mrm9084 2758caf
fixing async test
mrm9084 5d4d6fd
Merge remote-tracking branch 'upstream/main' into KeyVaultRefresh
mrm9084 f2f279f
mixing merge
mrm9084 4b7f2c5
fixing test after merge
mrm9084 7d95da6
Update testcase.py
mrm9084 bfaaa28
Secret Provider Base
mrm9084 0ab5e66
removing unused imports
mrm9084 6d68ba3
updating exception
mrm9084 15af7f3
updating resolve key vault references
mrm9084 b160963
Review comments
mrm9084 2cdff6d
fixing tests
mrm9084 f49340f
tox updates
mrm9084 4fb9532
Updating Tests
mrm9084 6f55701
Updating Async to be the same as sync
mrm9084 12dc565
Fixing formatting
mrm9084 2072e76
fixing tox and unneeded ""
mrm9084 4656f83
fixing tox items
mrm9084 08e5ada
fix cspell + tests recording
mrm9084 0599f68
Update test_async_secret_provider.py
mrm9084 78a0b0d
Merge remote-tracking branch 'upstream/main' into KeyVaultRefresh
mrm9084 35a05bf
Post Merge updates
mrm9084 8e04133
Move cache to shared code
mrm9084 f7ffe3f
removed unneeded disabled
mrm9084 3ebcf45
Update Secret Provider
mrm9084 8c2637d
Updating usage
mrm9084 cfad924
Merge remote-tracking branch 'upstream/main' into KeyVaultRefresh
mrm9084 8e14697
Update assets.json
mrm9084 53a0eeb
Updated to make secret refresh update dictionary
mrm9084 abddfd9
removing _secret_version_cache
mrm9084 0379631
Merge branch 'main' into KeyVaultRefresh
mrm9084 d5add3b
Update assets.json
mrm9084 f10365c
Update _secret_provider_base.py
mrm9084 1955caf
Update _secret_provider_base.py
mrm9084 b329284
Update _secret_provider_base.py
mrm9084 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
...on/azure-appconfiguration-provider/azure/appconfiguration/provider/_key_vault/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # ------------------------------------------------------------------------ | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for | ||
| # license information. | ||
| # ------------------------------------------------------------------------- | ||
|
|
||
| from ._secret_provider import SecretProvider | ||
| from ._secret_provider_base import _SecretProviderBase | ||
|
|
||
| __all__ = [ | ||
| "SecretProvider", | ||
| "_SecretProviderBase", | ||
| ] |
84 changes: 84 additions & 0 deletions
84
...-appconfiguration-provider/azure/appconfiguration/provider/_key_vault/_secret_provider.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # ------------------------------------------------------------------------ | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for | ||
| # license information. | ||
| # ------------------------------------------------------------------------- | ||
| from typing import Mapping, Any, Dict | ||
| from azure.appconfiguration import SecretReferenceConfigurationSetting # type:ignore # pylint:disable=no-name-in-module | ||
| from azure.keyvault.secrets import SecretClient, KeyVaultSecretIdentifier | ||
| from azure.core.exceptions import ServiceRequestError | ||
| from ._secret_provider_base import _SecretProviderBase | ||
|
|
||
| JSON = Mapping[str, Any] | ||
|
|
||
|
|
||
| class SecretProvider(_SecretProviderBase): | ||
|
|
||
| def __init__(self, **kwargs: Any) -> None: | ||
| super().__init__(**kwargs) | ||
| self._secret_clients: Dict[str, SecretClient] = {} | ||
| self._keyvault_credential = kwargs.pop("keyvault_credential", None) | ||
| self._secret_resolver = kwargs.pop("secret_resolver", None) | ||
| self._keyvault_client_configs = kwargs.pop("keyvault_client_configs", {}) | ||
|
|
||
| def resolve_keyvault_reference(self, config: SecretReferenceConfigurationSetting) -> str: | ||
| keyvault_identifier, vault_url = self.resolve_keyvault_reference_base(config) | ||
| if keyvault_identifier.source_id in self._secret_cache: | ||
| _, _, value = self._secret_cache[keyvault_identifier.source_id] | ||
| return value | ||
| if keyvault_identifier.source_id in self._secret_version_cache: | ||
| _, _, value = self._secret_version_cache[keyvault_identifier.source_id] | ||
| return value | ||
|
|
||
| return self.__get_secret_value(config.key, keyvault_identifier, vault_url) | ||
|
|
||
| def refresh_secrets(self) -> None: | ||
| original_cache = self._secret_cache.copy() | ||
| self._secret_cache.clear() | ||
mrm9084 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for source_id, (secret_identifier, key, _) in original_cache.items(): | ||
| self._secret_cache[source_id] = ( | ||
| secret_identifier, | ||
| key, | ||
| self.__get_secret_value(key, secret_identifier, secret_identifier.vault_url + "/"), | ||
| ) | ||
|
|
||
| def __get_secret_value(self, key: str, secret_identifier: KeyVaultSecretIdentifier, vault_url: str) -> str: | ||
| referenced_client = self._secret_clients.get(vault_url, None) | ||
|
|
||
| vault_config = self._keyvault_client_configs.get(vault_url, {}) | ||
| credential = vault_config.pop("credential", self._keyvault_credential) | ||
|
|
||
| if referenced_client is None and credential is not None: | ||
| referenced_client = SecretClient(vault_url=vault_url, credential=credential, **vault_config) | ||
| self._secret_clients[vault_url] = referenced_client | ||
|
|
||
| secret_value = None | ||
|
|
||
| if referenced_client: | ||
| try: | ||
| secret_value = referenced_client.get_secret( | ||
| secret_identifier.name, version=secret_identifier.version | ||
| ).value | ||
| except ServiceRequestError as e: | ||
| raise ValueError("Failed to retrieve secret from Key Vault") from e | ||
|
|
||
| if self._secret_resolver and secret_value is None: | ||
| secret_value = self._secret_resolver(secret_identifier.source_id) | ||
|
|
||
| return self._cache_value(key, secret_identifier, secret_value) | ||
|
|
||
| def close(self) -> None: | ||
| """ | ||
| Closes the connection to Azure App Configuration. | ||
| """ | ||
| for client in self._secret_clients.values(): | ||
| client.close() | ||
|
|
||
| def __enter__(self) -> "SecretProvider": | ||
| for client in self._secret_clients.values(): | ||
| client.__enter__() | ||
| return self | ||
|
|
||
| def __exit__(self, *args) -> None: | ||
| for client in self._secret_clients.values(): | ||
| client.__exit__() | ||
67 changes: 67 additions & 0 deletions
67
...onfiguration-provider/azure/appconfiguration/provider/_key_vault/_secret_provider_base.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| # ------------------------------------------------------------------------ | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT License. See License.txt in the project root for | ||
| # license information. | ||
| # ------------------------------------------------------------------------- | ||
| from typing import ( | ||
| Mapping, | ||
| Any, | ||
| TypeVar, | ||
| Optional, | ||
| Dict, | ||
| Tuple, | ||
| ) | ||
| from azure.appconfiguration import SecretReferenceConfigurationSetting # type:ignore # pylint:disable=no-name-in-module | ||
| from azure.keyvault.secrets import KeyVaultSecretIdentifier | ||
| from .._azureappconfigurationproviderbase import _RefreshTimer | ||
|
|
||
| JSON = Mapping[str, Any] | ||
| _T = TypeVar("_T") | ||
|
|
||
|
|
||
| class _SecretProviderBase: | ||
|
|
||
| def __init__(self, **kwargs: Any) -> None: | ||
| # [source_id, (KeyVaultSecretIdentifier, key, value)] | ||
| self._secret_cache: Dict[str, Tuple[KeyVaultSecretIdentifier, str, str]] = {} | ||
| self._secret_version_cache: Dict[str, Tuple[KeyVaultSecretIdentifier, str, str]] = {} | ||
mrm9084 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| self.uses_key_vault = ( | ||
| "keyvault_credential" in kwargs | ||
| or ("keyvault_client_configs" in kwargs and len(kwargs.get("keyvault_client_configs", {})) > 0) | ||
| or "secret_resolver" in kwargs | ||
| ) | ||
| self.secret_refresh_timer: Optional[_RefreshTimer] = ( | ||
| _RefreshTimer(refresh_interval=kwargs.pop("secret_refresh_interval", 60)) | ||
mrm9084 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if self.uses_key_vault and "secret_refresh_interval" in kwargs | ||
| else None | ||
| ) | ||
|
|
||
| def _cache_value(self, key: str, keyvault_identifier: KeyVaultSecretIdentifier, secret_value: Any) -> str: | ||
| if secret_value: | ||
| if keyvault_identifier.version: | ||
| self._secret_version_cache[keyvault_identifier.source_id] = (keyvault_identifier, key, secret_value) | ||
| else: | ||
| self._secret_cache[keyvault_identifier.source_id] = (keyvault_identifier, key, secret_value) | ||
| return secret_value | ||
|
|
||
| raise ValueError("No Secret Client found for Key Vault reference %s" % (keyvault_identifier.vault_url)) | ||
|
|
||
| def resolve_keyvault_reference_base( | ||
| self, config: SecretReferenceConfigurationSetting | ||
| ) -> Tuple[KeyVaultSecretIdentifier, str]: | ||
| if not self.uses_key_vault: | ||
| raise ValueError( | ||
| """ | ||
| Either a credential to Key Vault, custom Key Vault client, or a secret resolver must be set to resolve | ||
| Key Vault references. | ||
| """ | ||
| ) | ||
|
|
||
| if config.secret_id is None: | ||
| raise ValueError("Key Vault reference must have a uri value.") | ||
|
|
||
| keyvault_identifier = KeyVaultSecretIdentifier(config.secret_id) | ||
|
|
||
| vault_url = keyvault_identifier.vault_url + "/" | ||
|
|
||
| return keyvault_identifier, vault_url | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.