Skip to content
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

Verify challenge resource matches request domain #26141

Merged
merged 9 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions sdk/keyvault/azure-keyvault-administration/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Release History

## 4.2.0 (2022-09-19)

### Breaking Changes
- Clients verify the challenge resource matches the vault domain. This should affect few customers,
who can provide `verify_challenge_resource=False` to client constructors to disable.
See https://aka.ms/azsdk/blog/vault-uri for more information.

## 4.1.1 (2022-08-11)

### Other Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@ class KeyVaultAccessControlClient(KeyVaultClientBase):
"""Manages role-based access to Azure Key Vault.

:param str vault_url: URL of the vault the client will manage. This is also called the vault's "DNS Name".
:param credential: an object which can provide an access token for the vault, such as a credential from
You should validate that this URL references a valid Key Vault or Managed HSM resource.
See https://aka.ms/azsdk/blog/vault-uri for details.
:param credential: An object which can provide an access token for the vault, such as a credential from
:mod:`azure.identity`
:type credential: :class:`~azure.core.credentials.TokenCredential`

:keyword api_version: Version of the service API to use. Defaults to the most recent.
:paramtype api_version: ~azure.keyvault.administration.ApiVersion
:keyword bool verify_challenge_resource: Whether to verify the authentication challenge resource matches the Key
Vault or Managed HSM domain. Defaults to True.
mccoyp marked this conversation as resolved.
Show resolved Hide resolved
"""

# pylint:disable=protected-access
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,16 @@ class KeyVaultBackupClient(KeyVaultClientBase):
"""Performs Key Vault backup and restore operations.

:param str vault_url: URL of the vault on which the client will operate. This is also called the vault's "DNS Name".
:param credential: an object which can provide an access token for the vault, such as a credential from
You should validate that this URL references a valid Key Vault or Managed HSM resource.
See https://aka.ms/azsdk/blog/vault-uri for details.
:param credential: An object which can provide an access token for the vault, such as a credential from
:mod:`azure.identity`
:type credential: :class:`~azure.core.credentials.TokenCredential`

:keyword api_version: Version of the service API to use. Defaults to the most recent.
:paramtype api_version: ~azure.keyvault.administration.ApiVersion
:keyword bool verify_challenge_resource: Whether to verify the authentication challenge resource matches the Key
Vault or Managed HSM domain. Defaults to True.
"""

# pylint:disable=protected-access
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import time
from typing import TYPE_CHECKING
from urllib.parse import urlparse

from azure.core.pipeline.policies import AsyncBearerTokenCredentialPolicy

Expand All @@ -36,6 +37,7 @@ def __init__(self, credential: "AsyncTokenCredential", *scopes: str, **kwargs: "
super().__init__(credential, *scopes, **kwargs)
self._credential = credential
self._token = None # type: Optional[AccessToken]
self._verify_challenge_resource = kwargs.pop("verify_challenge_resource", True)

async def on_request(self, request: "PipelineRequest") -> None:
_enforce_tls(request)
Expand Down Expand Up @@ -69,6 +71,19 @@ async def on_challenge(self, request: "PipelineRequest", response: "PipelineResp
except ValueError:
return False

if self._verify_challenge_resource:
mccoyp marked this conversation as resolved.
Show resolved Hide resolved
resource_domain = urlparse(scope).netloc
if not resource_domain:
raise ValueError(f"The challenge contains invalid scope '{scope}'.")

request_domain = urlparse(request.http_request.url).netloc
if not request_domain.lower().endswith(f".{resource_domain.lower()}"):
mccoyp marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError(
f"The challenge resource '{resource_domain}' does not match the requested domain. Pass "
"`verify_challenge_resource=False` to your client's constructor to disable this verification. "
"See https://aka.ms/azsdk/blog/vault-uri for more information."
)

body = request.context.pop("key_vault_request_data", None)
request.http_request.set_text_body(body) # no-op when text is None
await self.authorize_request(request, scope, tenant_id=challenge.tenant_id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ def __init__(self, vault_url: str, credential: "AsyncTokenCredential", **kwargs:
from azure.core.pipeline.transport import AioHttpTransport
transport = AioHttpTransport(**kwargs)

verify_challenge = kwargs.pop("verify_challenge_resource", True)
self._client = _KeyVaultClient(
api_version=api_version,
pipeline=pipeline,
transport=transport,
authentication_policy=AsyncChallengeAuthPolicy(credential),
authentication_policy=AsyncChallengeAuthPolicy(credential, verify_challenge_resource=verify_challenge),
sdk_moniker=SDK_MONIKER,
http_logging_policy=http_logging_policy,
**kwargs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"""

import time
from urllib.parse import urlparse

from azure.core.exceptions import ServiceRequestError
from azure.core.pipeline import PipelineRequest
Expand Down Expand Up @@ -63,6 +64,7 @@ def __init__(self, credential, *scopes, **kwargs):
super(ChallengeAuthPolicy, self).__init__(credential, *scopes, **kwargs)
self._credential = credential
self._token = None # type: Optional[AccessToken]
self._verify_challenge_resource = kwargs.pop("verify_challenge_resource", True)

def on_request(self, request):
# type: (PipelineRequest) -> None
Expand Down Expand Up @@ -97,6 +99,19 @@ def on_challenge(self, request, response):
except ValueError:
return False

if self._verify_challenge_resource:
resource_domain = urlparse(scope).netloc
if not resource_domain:
raise ValueError(f"The challenge contains invalid scope '{scope}'.")

request_domain = urlparse(request.http_request.url).netloc
if not request_domain.lower().endswith(f".{resource_domain.lower()}"):
raise ValueError(
f"The challenge resource '{resource_domain}' does not match the requested domain. Pass "
"`verify_challenge_resource=False` to your client's constructor to disable this verification. "
"See https://aka.ms/azsdk/blog/vault-uri for more information."
)

body = request.context.pop("key_vault_request_data", None)
request.http_request.set_text_body(body) # no-op when text is None
self.authorize_request(request, scope, tenant_id=challenge.tenant_id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ def __init__(self, vault_url, credential, **kwargs):
{"x-ms-keyvault-network-info", "x-ms-keyvault-region", "x-ms-keyvault-service-version"}
)

verify_challenge = kwargs.pop("verify_challenge_resource", True)
self._client = _KeyVaultClient(
api_version=api_version,
pipeline=pipeline,
transport=transport,
authentication_policy=ChallengeAuthPolicy(credential),
authentication_policy=ChallengeAuthPolicy(credential, verify_challenge_resource=verify_challenge),
sdk_moniker=SDK_MONIKER,
http_logging_policy=http_logging_policy,
**kwargs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# Licensed under the MIT License.
# ------------------------------------

VERSION = "4.1.1"
VERSION = "4.2.0"
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ class KeyVaultAccessControlClient(AsyncKeyVaultClientBase):
"""Manages role-based access to Azure Key Vault.

:param str vault_url: URL of the vault the client will manage. This is also called the vault's "DNS Name".
:param credential: an object which can provide an access token for the vault, such as a credential from
:mod:`azure.identity`
You should validate that this URL references a valid Key Vault or Managed HSM resource.
See https://aka.ms/azsdk/blog/vault-uri for details.
:param credential: An object which can provide an access token for the vault, such as a credential from
:mod:`azure.identity.aio`
:type credential: :class:`~azure.core.credentials_async.AsyncTokenCredential`

:keyword api_version: Version of the service API to use. Defaults to the most recent.
:paramtype api_version: ~azure.keyvault.administration.ApiVersion
:keyword bool verify_challenge_resource: Whether to verify the authentication challenge resource matches the Key
Vault or Managed HSM domain. Defaults to True.
"""

# pylint:disable=protected-access
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@ class KeyVaultBackupClient(AsyncKeyVaultClientBase):
"""Performs Key Vault backup and restore operations.

:param str vault_url: URL of the vault on which the client will operate. This is also called the vault's "DNS Name".
:param credential: an object which can provide an access token for the vault, such as a credential from
You should validate that this URL references a valid Key Vault or Managed HSM resource.
See https://aka.ms/azsdk/blog/vault-uri for details.
:param credential: An object which can provide an access token for the vault, such as a credential from
:mod:`azure.identity.aio`
:type credential: :class:`~azure.core.credentials_async.AsyncTokenCredential`

:keyword api_version: Version of the service API to use. Defaults to the most recent.
:paramtype api_version: ~azure.keyvault.administration.ApiVersion
:keyword bool verify_challenge_resource: Whether to verify the authentication challenge resource matches the Key
Vault or Managed HSM domain. Defaults to True.
"""

# pylint:disable=protected-access
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

class BaseClientPreparer(AzureRecordedTestCase):
def __init__(self, **kwargs) -> None:
hsm_playback_url = "https://managedhsmvaultname.vault.azure.net"
hsm_playback_url = "https://managedhsmvaultname.managedhsm.azure.net"
container_playback_uri = "https://storagename.blob.core.windows.net/container"
playback_sas_token = "fake-sas"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

class BaseClientPreparer(AzureRecordedTestCase):
def __init__(self, **kwargs) -> None:
hsm_playback_url = "https://managedhsmvaultname.vault.azure.net"
hsm_playback_url = "https://managedhsmvaultname.managedhsm.azure.net"
container_playback_uri = "https://storagename.blob.core.windows.net/container"
playback_sas_token = "fake-sas"

Expand Down
4 changes: 2 additions & 2 deletions sdk/keyvault/azure-keyvault-administration/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def add_sanitizers(test_proxy):
azure_keyvault_url = azure_keyvault_url.rstrip("/")
keyvault_tenant_id = os.getenv("keyvault_tenant_id", "keyvault_tenant_id")
keyvault_subscription_id = os.getenv("keyvault_subscription_id", "keyvault_subscription_id")
azure_managedhsm_url = os.environ.get("azure_managedhsm_url","https://managedhsmvaultname.vault.azure.net")
azure_managedhsm_url = os.environ.get("azure_managedhsm_url","https://managedhsmvaultname.managedhsm.azure.net")
azure_managedhsm_url = azure_managedhsm_url.rstrip("/")
azure_attestation_uri = os.environ.get("azure_keyvault_attestation_url","https://fakeattestation.azurewebsites.net")
azure_attestation_uri = azure_attestation_uri.rstrip('/')
Expand All @@ -33,7 +33,7 @@ def add_sanitizers(test_proxy):
add_general_regex_sanitizer(regex=azure_keyvault_url, value="https://vaultname.vault.azure.net")
add_general_regex_sanitizer(regex=keyvault_tenant_id, value="00000000-0000-0000-0000-000000000000")
add_general_regex_sanitizer(regex=keyvault_subscription_id, value="00000000-0000-0000-0000-000000000000")
add_general_regex_sanitizer(regex=azure_managedhsm_url,value="https://managedhsmvaultname.vault.azure.net")
add_general_regex_sanitizer(regex=azure_managedhsm_url,value="https://managedhsmvaultname.managedhsm.azure.net")
add_general_regex_sanitizer(regex=azure_attestation_uri,value="https://fakeattestation.azurewebsites.net")
add_general_regex_sanitizer(regex=storage_name, value = "blob_storage_account_name")
add_general_regex_sanitizer(regex=storage_endpoint_suffix, value = "keyvault_endpoint_suffix")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"Entries": [
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2",
heaths marked this conversation as resolved.
Show resolved Hide resolved
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -200,7 +200,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.2",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -490,7 +490,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2",
"RequestMethod": "PUT",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -531,7 +531,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -566,7 +566,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -645,7 +645,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.2",
"RequestMethod": "DELETE",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -680,7 +680,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.2",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"Entries": [
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.3",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.3",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -200,7 +200,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.3",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleDefinitions?api-version=7.3",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -490,7 +490,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.3",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.3",
"RequestMethod": "PUT",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -531,7 +531,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.3",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.3",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -566,7 +566,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.3",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.3",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -645,7 +645,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.3",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments/some-uuid?api-version=7.3",
"RequestMethod": "DELETE",
"RequestHeaders": {
"Accept": "application/json",
Expand Down Expand Up @@ -680,7 +680,7 @@
}
},
{
"RequestUri": "https://managedhsmvaultname.vault.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.3",
"RequestUri": "https://managedhsmvaultname.managedhsm.azure.net/providers/Microsoft.Authorization/roleAssignments?api-version=7.3",
"RequestMethod": "GET",
"RequestHeaders": {
"Accept": "application/json",
Expand Down
Loading