Skip to content

Commit e8b6637

Browse files
authored
[Key Vault] Make issuer_name optional to support cert import (Azure#19141)
1 parent c1bbb7c commit e8b6637

File tree

7 files changed

+89
-87
lines changed

7 files changed

+89
-87
lines changed

sdk/keyvault/azure-keyvault-certificates/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This is the last version to support Python 3.5. The next version will require Py
55
### Changed
66
- Key Vault API version 7.2 is now the default
77
- Updated minimum `msrest` version to 0.6.21
8+
- The `issuer_name` parameter for `CertificatePolicy` is now optional
89

910
### Added
1011
- Added class `KeyVaultCertificateIdentifier` that parses out a full ID returned by Key Vault,

sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_client.py

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
from azure.core.paging import ItemPaged
3636

3737

38+
NO_SAN_OR_SUBJECT = "You need to set either subject or one of the subject alternative names parameters in the policy"
39+
40+
3841
class CertificateClient(KeyVaultClientBase):
3942
"""A high-level interface for managing a vault's certificates.
4043
@@ -68,17 +71,20 @@ def begin_create_certificate(self, certificate_name, policy, **kwargs):
6871
an :class:`~azure.core.exceptions.HttpResponseError`
6972
7073
:param str certificate_name: The name of the certificate.
71-
:param policy: The management policy for the certificate.
74+
:param policy: The management policy for the certificate. Either subject or one of the subject alternative
75+
name properties are required.
7276
:type policy:
73-
~azure.keyvault.certificates.CertificatePolicy
77+
~azure.keyvault.certificates.CertificatePolicy
7478
:keyword bool enabled: Whether the certificate is enabled for use.
7579
:keyword tags: Application specific metadata in the form of key-value pairs.
7680
:paramtype tags: dict[str, str]
7781
:returns: An LROPoller for the create certificate operation. Waiting on the poller
78-
gives you the certificate if creation is successful, the CertificateOperation if not.
82+
gives you the certificate if creation is successful, the CertificateOperation if not.
7983
:rtype: ~azure.core.polling.LROPoller[~azure.keyvault.certificates.KeyVaultCertificate or
80-
~azure.keyvault.certificates.CertificateOperation]
81-
:raises: :class:`~azure.core.exceptions.HttpResponseError`
84+
~azure.keyvault.certificates.CertificateOperation]
85+
:raises:
86+
:class:`ValueError` if the certificate policy is invalid,
87+
:class:`~azure.core.exceptions.HttpResponseError` for other errors.
8288
8389
Keyword arguments
8490
- *enabled (bool)* - Determines whether the object is enabled.
@@ -92,12 +98,14 @@ def begin_create_certificate(self, certificate_name, policy, **kwargs):
9298
:caption: Create a certificate
9399
:dedent: 8
94100
"""
101+
if not (policy.san_emails or policy.san_user_principal_names or policy.san_dns_names or policy.subject):
102+
raise ValueError(NO_SAN_OR_SUBJECT)
103+
95104
polling_interval = kwargs.pop("_polling_interval", None)
96105
if polling_interval is None:
97106
polling_interval = 5
98107
enabled = kwargs.pop("enabled", None)
99108

100-
101109
if enabled is not None:
102110
attributes = self._models.CertificateAttributes(enabled=enabled)
103111
else:
@@ -106,7 +114,7 @@ def begin_create_certificate(self, certificate_name, policy, **kwargs):
106114
parameters = self._models.CertificateCreateParameters(
107115
certificate_policy=policy._to_certificate_policy_bundle(),
108116
certificate_attributes=attributes,
109-
tags=kwargs.pop("tags", None)
117+
tags=kwargs.pop("tags", None),
110118
)
111119

112120
cert_bundle = self._client.create_certificate(
@@ -332,7 +340,6 @@ def begin_recover_deleted_certificate(self, certificate_name, **kwargs):
332340

333341
return KeyVaultOperationPoller(polling_method)
334342

335-
336343
@distributed_trace
337344
def import_certificate(self, certificate_name, certificate_bytes, **kwargs):
338345
# type: (str, bytes, **Any) -> KeyVaultCertificate
@@ -459,8 +466,7 @@ def update_certificate_properties(self, certificate_name, version=None, **kwargs
459466
attributes = None
460467

461468
parameters = self._models.CertificateUpdateParameters(
462-
certificate_attributes=attributes,
463-
tags=kwargs.pop("tags", None)
469+
certificate_attributes=attributes, tags=kwargs.pop("tags", None)
464470
)
465471

466472
bundle = self._client.update_certificate(
@@ -528,7 +534,8 @@ def restore_certificate_backup(self, backup, **kwargs):
528534
bundle = self._client.restore_certificate(
529535
vault_base_url=self.vault_url,
530536
parameters=self._models.CertificateRestoreParameters(certificate_bundle_backup=backup),
531-
error_map=_error_map, **kwargs
537+
error_map=_error_map,
538+
**kwargs
532539
)
533540
return KeyVaultCertificate._from_certificate_bundle(certificate_bundle=bundle)
534541

@@ -795,9 +802,7 @@ def merge_certificate(self, certificate_name, x509_certificates, **kwargs):
795802
attributes = None
796803

797804
parameters = self._models.CertificateMergeParameters(
798-
x509_certificates=x509_certificates,
799-
certificate_attributes=attributes,
800-
tags=kwargs.pop("tags", None)
805+
x509_certificates=x509_certificates, certificate_attributes=attributes, tags=kwargs.pop("tags", None)
801806
)
802807

803808
bundle = self._client.merge_certificate(
@@ -884,9 +889,7 @@ def create_issuer(self, issuer_name, provider, **kwargs):
884889
else:
885890
admin_details = None
886891
if organization_id or admin_details:
887-
organization_details = self._models.OrganizationDetails(
888-
id=organization_id, admin_details=admin_details
889-
)
892+
organization_details = self._models.OrganizationDetails(id=organization_id, admin_details=admin_details)
890893
else:
891894
organization_details = None
892895
if enabled is not None:
@@ -902,11 +905,7 @@ def create_issuer(self, issuer_name, provider, **kwargs):
902905
)
903906

904907
issuer_bundle = self._client.set_certificate_issuer(
905-
vault_base_url=self.vault_url,
906-
issuer_name=issuer_name,
907-
parameter=parameters,
908-
error_map=_error_map,
909-
**kwargs
908+
vault_base_url=self.vault_url, issuer_name=issuer_name, parameter=parameters, error_map=_error_map, **kwargs
910909
)
911910
return CertificateIssuer._from_issuer_bundle(issuer_bundle=issuer_bundle)
912911

@@ -951,9 +950,7 @@ def update_issuer(self, issuer_name, **kwargs):
951950
else:
952951
admin_details = None
953952
if organization_id or admin_details:
954-
organization_details = self._models.OrganizationDetails(
955-
id=organization_id, admin_details=admin_details
956-
)
953+
organization_details = self._models.OrganizationDetails(id=organization_id, admin_details=admin_details)
957954
else:
958955
organization_details = None
959956
if enabled is not None:
@@ -965,15 +962,11 @@ def update_issuer(self, issuer_name, **kwargs):
965962
provider=kwargs.pop("provider", None),
966963
credentials=issuer_credentials,
967964
organization_details=organization_details,
968-
attributes=issuer_attributes
965+
attributes=issuer_attributes,
969966
)
970967

971968
issuer_bundle = self._client.update_certificate_issuer(
972-
vault_base_url=self.vault_url,
973-
issuer_name=issuer_name,
974-
parameter=parameters,
975-
error_map=_error_map,
976-
**kwargs
969+
vault_base_url=self.vault_url, issuer_name=issuer_name, parameter=parameters, error_map=_error_map, **kwargs
977970
)
978971
return CertificateIssuer._from_issuer_bundle(issuer_bundle=issuer_bundle)
979972

sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/_models.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -618,17 +618,19 @@ def request_id(self):
618618
class CertificatePolicy(object):
619619
"""Management policy for a certificate.
620620
621-
:param str issuer_name: Name of the referenced issuer object or reserved names; for example,
622-
'Self' or 'Unknown"
621+
:param Optional[str] issuer_name: Optional. Name of the referenced issuer object or reserved names; for example,
622+
:attr:`~azure.keyvault.certificates.WellKnownIssuerNames.self` or
623+
:attr:`~azure.keyvault.certificates.WellKnownIssuerNames.unknown`
623624
:keyword str subject: The subject name of the certificate. Should be a valid X509
624-
distinguished name. Either subject or one of the subject alternative name parameters
625-
are required.
625+
distinguished name. Either subject or one of the subject alternative name parameters are required for
626+
creating a certificate. This will be ignored when importing a certificate; the subject will be parsed from
627+
the imported certificate.
626628
:keyword Iterable[str] san_emails: Subject alternative emails of the X509 object. Either
627-
subject or one of the subject alternative name parameters are required.
629+
subject or one of the subject alternative name parameters are required for creating a certificate.
628630
:keyword Iterable[str] san_dns_names: Subject alternative DNS names of the X509 object. Either
629-
subject or one of the subject alternative name parameters are required.
631+
subject or one of the subject alternative name parameters are required for creating a certificate.
630632
:keyword Iterable[str] san_user_principal_names: Subject alternative user principal names of the X509 object.
631-
Either subject or one of the subject alternative name parameters are required.
633+
Either subject or one of the subject alternative name parameters are required for creating a certificate.
632634
:keyword bool exportable: Indicates if the private key can be exported. For valid values,
633635
see KeyType.
634636
:keyword key_type: The type of key pair to be used for the certificate.
@@ -659,7 +661,7 @@ class CertificatePolicy(object):
659661
# pylint:disable=too-many-instance-attributes
660662
def __init__(
661663
self,
662-
issuer_name, # type: str
664+
issuer_name=None, # type: Optional[str]
663665
**kwargs # type: Any
664666
):
665667
# type: (...) -> None
@@ -682,12 +684,6 @@ def __init__(
682684
self._san_dns_names = kwargs.pop("san_dns_names", None) or None
683685
self._san_user_principal_names = kwargs.pop("san_user_principal_names", None) or None
684686

685-
if not (
686-
self._san_emails or self._san_user_principal_names or self._san_dns_names or self._subject
687-
):
688-
raise ValueError("You need to set either subject or one of the subject alternative names " +
689-
"parameters")
690-
691687
@classmethod
692688
def get_default(cls):
693689
return cls(issuer_name=WellKnownIssuerNames.self, subject="CN=DefaultPolicy")
@@ -987,7 +983,7 @@ def lifetime_actions(self):
987983

988984
@property
989985
def issuer_name(self):
990-
# type: () -> str
986+
# type: () -> Optional[str]
991987
"""Name of the referenced issuer object or reserved names for the issuer
992988
of the certificate.
993989

sdk/keyvault/azure-keyvault-certificates/azure/keyvault/certificates/aio/_client.py

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
IssuerProperties,
2424
)
2525
from ._polling_async import CreateCertificatePollerAsync
26+
from .._client import NO_SAN_OR_SUBJECT
2627
from .._shared import AsyncKeyVaultClientBase
2728
from .._shared._polling_async import AsyncDeleteRecoverPollingMethod
2829
from .._shared.exceptions import error_map as _error_map
@@ -62,17 +63,20 @@ async def create_certificate(
6263
an :class:`~azure.core.exceptions.HttpResponseError`
6364
6465
:param str certificate_name: The name of the certificate.
65-
:param policy: The management policy for the certificate.
66+
:param policy: The management policy for the certificate. Either subject or one of the subject alternative
67+
name properties are required.
6668
:type policy:
67-
~azure.keyvault.certificates.CertificatePolicy
69+
~azure.keyvault.certificates.CertificatePolicy
6870
:keyword bool enabled: Whether the certificate is enabled for use.
6971
:keyword tags: Application specific metadata in the form of key-value pairs.
7072
:paramtype tags: dict[str, str]
7173
:returns: A coroutine for the creation of the certificate. Awaiting the coroutine
72-
returns the created KeyVaultCertificate if creation is successful, the CertificateOperation if not.
74+
returns the created KeyVaultCertificate if creation is successful, the CertificateOperation if not.
7375
:rtype: ~azure.keyvault.certificates.KeyVaultCertificate or
74-
~azure.keyvault.certificates.CertificateOperation
75-
:raises: :class:`~azure.core.exceptions.HttpResponseError`
76+
~azure.keyvault.certificates.CertificateOperation
77+
:raises:
78+
:class:`ValueError` if the certificate policy is invalid,
79+
:class:`~azure.core.exceptions.HttpResponseError` for other errors.
7680
7781
Example:
7882
.. literalinclude:: ../tests/test_examples_certificates_async.py
@@ -82,6 +86,9 @@ async def create_certificate(
8286
:caption: Create a certificate
8387
:dedent: 8
8488
"""
89+
if not (policy.san_emails or policy.san_user_principal_names or policy.san_dns_names or policy.subject):
90+
raise ValueError(NO_SAN_OR_SUBJECT)
91+
8592
polling_interval = kwargs.pop("_polling_interval", None)
8693
if polling_interval is None:
8794
polling_interval = 5
@@ -95,7 +102,7 @@ async def create_certificate(
95102
parameters = self._models.CertificateCreateParameters(
96103
certificate_policy=policy._to_certificate_policy_bundle(),
97104
certificate_attributes=attributes,
98-
tags=kwargs.pop("tags", None)
105+
tags=kwargs.pop("tags", None),
99106
)
100107

101108
cert_bundle = await self._client.create_certificate(
@@ -435,8 +442,7 @@ async def update_certificate_properties(
435442
attributes = None
436443

437444
parameters = self._models.CertificateUpdateParameters(
438-
certificate_attributes=attributes,
439-
tags=kwargs.pop("tags", None)
445+
certificate_attributes=attributes, tags=kwargs.pop("tags", None)
440446
)
441447

442448
bundle = await self._client.update_certificate(
@@ -774,9 +780,7 @@ async def merge_certificate(
774780
attributes = None
775781

776782
parameters = self._models.CertificateMergeParameters(
777-
x509_certificates=x509_certificates,
778-
certificate_attributes=attributes,
779-
tags=kwargs.pop("tags", None)
783+
x509_certificates=x509_certificates, certificate_attributes=attributes, tags=kwargs.pop("tags", None)
780784
)
781785

782786
bundle = await self._client.merge_certificate(
@@ -861,9 +865,7 @@ async def create_issuer(self, issuer_name: str, provider: str, **kwargs: "Any")
861865
else:
862866
admin_details = None
863867
if organization_id or admin_details:
864-
organization_details = self._models.OrganizationDetails(
865-
id=organization_id, admin_details=admin_details
866-
)
868+
organization_details = self._models.OrganizationDetails(id=organization_id, admin_details=admin_details)
867869
else:
868870
organization_details = None
869871
if enabled is not None:
@@ -879,11 +881,7 @@ async def create_issuer(self, issuer_name: str, provider: str, **kwargs: "Any")
879881
)
880882

881883
issuer_bundle = await self._client.set_certificate_issuer(
882-
vault_base_url=self.vault_url,
883-
issuer_name=issuer_name,
884-
parameter=parameters,
885-
error_map=_error_map,
886-
**kwargs
884+
vault_base_url=self.vault_url, issuer_name=issuer_name, parameter=parameters, error_map=_error_map, **kwargs
887885
)
888886
return CertificateIssuer._from_issuer_bundle(issuer_bundle=issuer_bundle)
889887

@@ -928,9 +926,7 @@ async def update_issuer(self, issuer_name: str, **kwargs: "Any") -> CertificateI
928926
else:
929927
admin_details = None
930928
if organization_id or admin_details:
931-
organization_details = self._models.OrganizationDetails(
932-
id=organization_id, admin_details=admin_details
933-
)
929+
organization_details = self._models.OrganizationDetails(id=organization_id, admin_details=admin_details)
934930
else:
935931
organization_details = None
936932
if enabled is not None:
@@ -942,15 +938,11 @@ async def update_issuer(self, issuer_name: str, **kwargs: "Any") -> CertificateI
942938
provider=kwargs.pop("provider", None),
943939
credentials=issuer_credentials,
944940
organization_details=organization_details,
945-
attributes=issuer_attributes
941+
attributes=issuer_attributes,
946942
)
947943

948944
issuer_bundle = await self._client.update_certificate_issuer(
949-
vault_base_url=self.vault_url,
950-
issuer_name=issuer_name,
951-
parameter=parameters,
952-
error_map=_error_map,
953-
**kwargs
945+
vault_base_url=self.vault_url, issuer_name=issuer_name, parameter=parameters, error_map=_error_map, **kwargs
954946
)
955947
return CertificateIssuer._from_issuer_bundle(issuer_bundle=issuer_bundle)
956948

sdk/keyvault/azure-keyvault-certificates/tests/test_certificates_client.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
CertificateContentType,
2525
LifetimeAction,
2626
CertificateIssuer,
27-
IssuerProperties
27+
IssuerProperties,
28+
WellKnownIssuerNames
2829
)
30+
from azure.keyvault.certificates._client import NO_SAN_OR_SUBJECT
2931
import pytest
3032

3133
from _shared.test_case import KeyVaultTestCase
@@ -680,6 +682,19 @@ def test_list_deleted_certificates(self, client, **kwargs):
680682
assert "The 'include_pending' parameter to `list_deleted_certificates` is only available for API versions v7.0 and up" in str(excinfo.value)
681683

682684

685+
def test_policy_expected_errors_for_create_cert():
686+
"""Either a subject or subject alternative name property are required for creating a certificate"""
687+
client = CertificateClient("...", object())
688+
689+
with pytest.raises(ValueError, match=NO_SAN_OR_SUBJECT):
690+
policy = CertificatePolicy()
691+
client.begin_create_certificate("...", policy=policy)
692+
693+
with pytest.raises(ValueError, match=NO_SAN_OR_SUBJECT):
694+
policy = CertificatePolicy(issuer_name=WellKnownIssuerNames.self)
695+
client.begin_create_certificate("...", policy=policy)
696+
697+
683698
def test_service_headers_allowed_in_logs():
684699
service_headers = {"x-ms-keyvault-network-info", "x-ms-keyvault-region", "x-ms-keyvault-service-version"}
685700
client = CertificateClient("...", object())

0 commit comments

Comments
 (0)