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

[Key Vault] Implement 7.3-preview with secure key release #18055

Merged
merged 11 commits into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,32 @@
from ._parse_id import parse_key_vault_key_id
from ._shared.client_base import ApiVersion
from ._shared import KeyVaultResourceId
from ._models import DeletedKey, JsonWebKey, KeyProperties, KeyVaultKey
from ._models import (
DeletedKey,
JsonWebKey,
KeyExportParameters,
KeyProperties,
KeyReleaseParameters,
KeyReleasePolicy,
KeyReleaseResult,
KeyVaultKey,
)
from ._client import KeyClient

__all__ = [
"ApiVersion",
"KeyClient",
"JsonWebKey",
"KeyExportParameters",
"KeyVaultKey",
"KeyCurveName",
"KeyOperation",
"KeyType",
"DeletedKey",
"KeyProperties",
"KeyReleaseParameters",
"KeyReleasePolicy",
"KeyReleaseResult",
"parse_key_vault_key_id",
"KeyVaultResourceId"
]
Expand Down
96 changes: 87 additions & 9 deletions sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ._shared import KeyVaultClientBase
from ._shared.exceptions import error_map as _error_map
from ._shared._polling import DeleteRecoverPollingMethod, KeyVaultOperationPoller
from ._models import KeyVaultKey, KeyProperties, DeletedKey
from ._models import KeyVaultKey, KeyProperties, KeyReleaseResult, DeletedKey

try:
from typing import TYPE_CHECKING
Expand All @@ -20,7 +20,7 @@
from typing import Any, List, Optional, Union
from datetime import datetime
from azure.core.paging import ItemPaged
from ._models import JsonWebKey
from ._models import JsonWebKey, KeyExportParameters, KeyReleaseParameters


class KeyClient(KeyVaultClientBase):
Expand Down Expand Up @@ -67,6 +67,9 @@ def create_key(self, name, key_type, **kwargs):
:paramtype tags: dict[str, str]
:keyword ~datetime.datetime not_before: Not before date of the key in UTC
:keyword ~datetime.datetime expires_on: Expiry date of the key in UTC
:keyword bool exportable: Whether the private key can be exported.
:param release_policy: The policy rules under which the key can be exported.
:type release_policy: ~azure.keyvault.keys.KeyReleasePolicy
mccoyp marked this conversation as resolved.
Show resolved Hide resolved
:returns: The created key
:rtype: ~azure.keyvault.keys.KeyVaultKey
:raises: :class:`~azure.core.exceptions.HttpResponseError`
Expand All @@ -82,18 +85,27 @@ def create_key(self, name, key_type, **kwargs):
enabled = kwargs.pop("enabled", None)
not_before = kwargs.pop("not_before", None)
expires_on = kwargs.pop("expires_on", None)
if enabled is not None or not_before is not None or expires_on is not None:
attributes = self._models.KeyAttributes(enabled=enabled, not_before=not_before, expires=expires_on)
exportable = kwargs.pop("exportable", None)

if enabled is not None or not_before is not None or expires_on is not None or exportable is not None:
attributes = self._models.KeyAttributes(
enabled=enabled, not_before=not_before, expires=expires_on, exportable=exportable
)
else:
attributes = None

policy = kwargs.pop("release_policy", None)
release_policy = None if policy is None else self._models.KeyReleasePolicy(
data=policy.data, content_type=policy.content_type
)
parameters = self._models.KeyCreateParameters(
kty=key_type,
key_size=kwargs.pop("size", None),
key_attributes=attributes,
key_ops=kwargs.pop("key_operations", None),
tags=kwargs.pop("tags", None),
curve=kwargs.pop("curve", None)
curve=kwargs.pop("curve", None),
release_policy=release_policy
)

bundle = self._client.create_key(
Expand Down Expand Up @@ -123,6 +135,9 @@ def create_rsa_key(self, name, **kwargs):
:paramtype tags: dict[str, str]
:keyword ~datetime.datetime not_before: Not before date of the key in UTC
:keyword ~datetime.datetime expires_on: Expiry date of the key in UTC
:keyword bool exportable: Whether the private key can be exported.
:param release_policy: The policy rules under which the key can be exported.
:type release_policy: ~azure.keyvault.keys.KeyReleasePolicy
:returns: The created key
:rtype: ~azure.keyvault.keys.KeyVaultKey
:raises: :class:`~azure.core.exceptions.HttpResponseError`
Expand Down Expand Up @@ -157,6 +172,9 @@ def create_ec_key(self, name, **kwargs):
:paramtype tags: dict[str, str]
:keyword ~datetime.datetime not_before: Not before date of the key in UTC
:keyword ~datetime.datetime expires_on: Expiry date of the key in UTC
:keyword bool exportable: Whether the private key can be exported.
:param release_policy: The policy rules under which the key can be exported.
:type release_policy: ~azure.keyvault.keys.KeyReleasePolicy
:returns: The created key
:rtype: ~azure.keyvault.keys.KeyVaultKey
:raises: :class:`~azure.core.exceptions.HttpResponseError`
Expand Down Expand Up @@ -409,7 +427,6 @@ def begin_recover_deleted_key(self, name, **kwargs):

return KeyVaultOperationPoller(polling_method)


@distributed_trace
def update_key_properties(self, name, version=None, **kwargs):
# type: (str, Optional[str], **Any) -> KeyVaultKey
Expand Down Expand Up @@ -536,23 +553,35 @@ def import_key(self, name, key, **kwargs):
:paramtype tags: dict[str, str]
:keyword ~datetime.datetime not_before: Not before date of the key in UTC
:keyword ~datetime.datetime expires_on: Expiry date of the key in UTC
:keyword bool exportable: Whether the private key can be exported.
:param release_policy: The policy rules under which the key can be exported.
:type release_policy: ~azure.keyvault.keys.KeyReleasePolicy
:returns: The imported key
:rtype: ~azure.keyvault.keys.KeyVaultKey
:raises: :class:`~azure.core.exceptions.HttpResponseError`
"""
enabled = kwargs.pop("enabled", None)
not_before = kwargs.pop("not_before", None)
expires_on = kwargs.pop("expires_on", None)
if enabled is not None or not_before is not None or expires_on is not None:
attributes = self._models.KeyAttributes(enabled=enabled, not_before=not_before, expires=expires_on)
exportable = kwargs.pop("exportable", None)

if enabled is not None or not_before is not None or expires_on is not None or exportable is not None:
attributes = self._models.KeyAttributes(
enabled=enabled, not_before=not_before, expires=expires_on, exportable=exportable
)
else:
attributes = None

policy = kwargs.pop("release_policy", None)
release_policy = None if policy is None else self._models.KeyReleasePolicy(
data=policy.data, content_type=policy.content_type
)
parameters = self._models.KeyImportParameters(
key=key._to_generated_model(),
key_attributes=attributes,
hsm=kwargs.pop("hardware_protected", None),
tags=kwargs.pop("tags", None)
tags=kwargs.pop("tags", None),
release_policy=release_policy
)

bundle = self._client.import_key(
Expand All @@ -563,3 +592,52 @@ def import_key(self, name, key, **kwargs):
**kwargs
)
return KeyVaultKey._from_key_bundle(bundle)

@distributed_trace
def export_key(self, name, version, parameters, **kwargs):
# type: (str, str, KeyExportParameters, **Any) -> KeyVaultKey
"""Exports a key.

The export key operation is applicable to all key types. The target key must be marked
exportable. This operation requires the keys/export permission.

:param str name: The name of the key to export.
:param str version: A specific version of the key to export.
:param parameters: The parameters for the export operation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to include the name and version in the parameters, rather than all three. In fact, in .NET we'd have to change the order anyway, since version is optional and we can't have required parameters after optional parameters. But looking at similar cases, we tend to just consolidate into an "options" or "parameters" (in this case, since it's required, probably the latter) parameter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remaining parameters are all optional according to the latest swagger changes so I think it makes the most sense to make them kwargs here and drop the class. For other languages that use a parameter object pattern, it might make more sense to move the key name/version into KeyExportParameters, like you said.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually hoist up some optional parameters like version because they are important and common. We do that in other APIs. Maybe not parameters, but it's something to discuss further in the channel. For now, not blocking.

:type parameters: ~azure.keyvault.keys.KeyExportParameters
:return: The exported key.
:rtype: ~azure.keyvault.keys.KeyVaultKey
:raises: :class:`~azure.core.exceptions.HttpResponseError`
"""
key_parameters = self._models.KeyExportParameters(
kek=parameters.key._to_generated_model(), enc=parameters.algorithm
)
bundle = self._client.export(self.vault_url, name, version, key_parameters, **kwargs)
return KeyVaultKey._from_key_bundle(bundle)

@distributed_trace
def release_key(self, name, version, parameters, **kwargs):
mccoyp marked this conversation as resolved.
Show resolved Hide resolved
# type: (str, str, KeyReleaseParameters, **Any) -> KeyReleaseResult
"""Releases a key.

The release key operation is applicable to all key types. The target key must be marked
exportable. This operation requires the keys/release permission.

:param str name: The name of the key to get.
:param str version: A specific version of the key to release.
:param parameters: The parameters for the key release operation.
:type parameters: ~azure.keyvault.keys.KeyReleaseParameters
:return: The result of the key release.
:rtype: ~azure.keyvault.keys.KeyReleaseResult
:raises: :class:`~azure.core.exceptions.HttpResponseError`
"""
result = self._client.release(
vault_base_url=self._vault_url,
key_name=name,
key_version=version,
parameters=self._models.KeyReleaseParameters(
environment=parameters.environment, nonce=parameters.nonce, enc=parameters.algorithm
),
**kwargs
)
return KeyReleaseResult(result.value)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class KeyOperation(str, Enum):
verify = "verify"
wrap_key = "wrapKey"
unwrap_key = "unwrapKey"
export = "export"


class KeyType(str, Enum):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
# pylint: disable=unused-import,ungrouped-imports
from typing import Any, Optional

from azure.core.pipeline.transport import HttpRequest, HttpResponse

class _SDKClient(object):
def __init__(self, *args, **kwargs):
"""This is a fake class to support current implemetation of MultiApiClientMixin."
Expand Down Expand Up @@ -120,7 +122,7 @@ def __init__(
profile=KnownProfiles.default, # type: KnownProfiles
**kwargs # type: Any
):
if api_version == '2016-10-01' or api_version == '7.0' or api_version == '7.1' or api_version == '7.2-preview':
if api_version == '2016-10-01' or api_version == '7.0' or api_version == '7.1' or api_version == '7.2-preview' or api_version == '7.3-preview':
base_url = '{vaultBaseUrl}'
else:
raise ValueError("API version {} is not available".format(api_version))
Expand All @@ -143,6 +145,7 @@ def models(cls, api_version=DEFAULT_API_VERSION):
* 7.0: :mod:`v7_0.models<azure.keyvault.v7_0.models>`
* 7.1: :mod:`v7_1.models<azure.keyvault.v7_1.models>`
* 7.2-preview: :mod:`v7_2_preview.models<azure.keyvault.v7_2_preview.models>`
* 7.3-preview: :mod:`v7_3_preview.models<azure.keyvault.v7_3_preview.models>`
"""
if api_version == '2016-10-01':
from .v2016_10_01 import models
Expand All @@ -156,6 +159,9 @@ def models(cls, api_version=DEFAULT_API_VERSION):
elif api_version == '7.2-preview':
from .v7_2_preview import models
return models
elif api_version == '7.3-preview':
from .v7_3_preview import models
return models
raise ValueError("API version {} is not available".format(api_version))

def close(self):
Expand Down
Loading