diff --git a/.whitesource b/.whitesource new file mode 100644 index 0000000..0d7ea09 --- /dev/null +++ b/.whitesource @@ -0,0 +1,13 @@ +{ + "scanSettings": { + "baseBranches": [] + }, + "checkRunSettings": { + "vulnerableCheckRunConclusionLevel": "failure", + "displayMode": "diff" + }, + "issueSettings": { + "minSeverityLevel": "LOW", + "issueType": "DEPENDENCY" + } +} \ No newline at end of file diff --git a/certbot_azure/azure_agw.py b/certbot_azure/azure_agw.py index 718fcbc..934cf75 100644 --- a/certbot_azure/azure_agw.py +++ b/certbot_azure/azure_agw.py @@ -3,18 +3,20 @@ from __future__ import print_function import os -import sys import logging import time import OpenSSL import base64 + try: from secrets import token_urlsafe except ImportError: from os import urandom + def token_urlsafe(nbytes=None): return urandom(nbytes) + import zope.component import zope.interface @@ -29,16 +31,22 @@ def token_urlsafe(nbytes=None): from msrestazure.azure_exceptions import CloudError -MSDOCS = 'https://docs.microsoft.com/' -ACCT_URL = MSDOCS + 'python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file' -AZURE_CLI_URL = MSDOCS + 'cli/azure/install-azure-cli?view=azure-cli-latest' -AZURE_CLI_COMMAND = ("az ad sp create-for-rbac" - " --name Certbot --sdk-auth" - " --scope /subscriptions//resourceGroups/" - " > mycredentials.json") +MSDOCS = "https://docs.microsoft.com/" +ACCT_URL = ( + MSDOCS + + "python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file" +) +AZURE_CLI_URL = MSDOCS + "cli/azure/install-azure-cli?view=azure-cli-latest" +AZURE_CLI_COMMAND = ( + "az ad sp create-for-rbac" + " --name Certbot --sdk-auth" + " --scope /subscriptions//resourceGroups/" + " > mycredentials.json" +) logger = logging.getLogger(__name__) + @zope.interface.implementer(interfaces.IInstaller) @zope.interface.provider(interfaces.IPluginFactory) class Installer(common.Plugin): @@ -47,52 +55,59 @@ class Installer(common.Plugin): @classmethod def add_parser_arguments(cls, add): - add('credentials', + add( + "credentials", help=( - 'Path to Azure service account JSON file. If you already have a Service ' + - 'Principal with the required permissions, you can create your own file as per ' + - 'the JSON file format at {0}. ' + - 'Otherwise, you can create a new Service Principal using the Azure CLI ' + - '(available at {1}) by running "az login" then "{2}"' + - 'This will create file "mycredentials.json" which you should secure, then ' + - 'specify with this option or with the AZURE_AUTH_LOCATION environment variable.') - .format(ACCT_URL, AZURE_CLI_URL, AZURE_CLI_COMMAND), - default=None) - add('resource-group', - help=('Resource Group in which the DNS zone is located'), - default=None) - add('app-gateway-name', - help=('Name of the application gateway'), - default=None) + "Path to Azure service account JSON file. If you already have a Service " + + "Principal with the required permissions, you can create your own file as per " + + "the JSON file format at {0}. " + + "Otherwise, you can create a new Service Principal using the Azure CLI " + + '(available at {1}) by running "az login" then "{2}"' + + 'This will create file "mycredentials.json" which you should secure, then ' + + "specify with this option or with the AZURE_AUTH_LOCATION environment variable." + ).format(ACCT_URL, AZURE_CLI_URL, AZURE_CLI_COMMAND), + default=None, + ) + add( + "resource-group", + help=("Resource Group in which the DNS zone is located"), + default=None, + ) + add("app-gateway-name", help=("Name of the application gateway"), default=None) def __init__(self, *args, **kwargs): super(Installer, self).__init__(*args, **kwargs) self._setup_credentials() - self.azure_client = _AzureClient(self.conf('resource-group'), self.conf('credentials')) - + self.azure_client = _AzureClient( + self.conf("resource-group"), self.conf("credentials") + ) def _setup_credentials(self): - if self.conf('resource-group') is None: - raise errors.PluginError('Please specify a resource group using ' - '--azure-agw-resource-group ') + if self.conf("resource-group") is None: + raise errors.PluginError( + "Please specify a resource group using " + "--azure-agw-resource-group " + ) - if self.conf('app-gateway-name') is None: - raise errors.PluginError('Please specify the app gateway name ' - '--azure-agw-resource-group ') + if self.conf("app-gateway-name") is None: + raise errors.PluginError( + "Please specify the app gateway name " + "--azure-agw-resource-group " + ) - if self.conf( - 'credentials') is None and 'AZURE_AUTH_LOCATION' not in os.environ: + if self.conf("credentials") is None and "AZURE_AUTH_LOCATION" not in os.environ: raise errors.PluginError( - 'Please specify credentials file using the ' - 'AZURE_AUTH_LOCATION environment variable or ' - 'using --azure-agw-credentials ') + "Please specify credentials file using the " + "AZURE_AUTH_LOCATION environment variable or " + "using --azure-agw-credentials " + ) def prepare(self): # pylint: disable=missing-docstring,no-self-use pass # pragma: no cover def more_info(self): # pylint: disable=missing-docstring,no-self-use - return ("") + return "" def get_all_names(self): # pylint: disable=missing-docstring,no-self-use pass # pragma: no cover @@ -102,9 +117,13 @@ def deploy_cert(self, domain, cert_path, key_path, chain_path, fullchain_path): Upload Certificate to the app gateway """ - self.azure_client.update_agw(self.conf('app-gateway-name'),domain, key_path, fullchain_path) + self.azure_client.update_agw( + self.conf("app-gateway-name"), domain, key_path, fullchain_path + ) - def enhance(self, domain, enhancement, options=None): # pylint: disable=missing-docstring,no-self-use + def enhance( + self, domain, enhancement, options=None + ): # pylint: disable=missing-docstring,no-self-use pass # pragma: no cover def supported_enhancements(self): # pylint: disable=missing-docstring,no-self-use @@ -113,10 +132,14 @@ def supported_enhancements(self): # pylint: disable=missing-docstring,no-self-u def get_all_certs_keys(self): # pylint: disable=missing-docstring,no-self-use pass # pragma: no cover - def save(self, title=None, temporary=False): # pylint: disable=missing-docstring,no-self-use + def save( + self, title=None, temporary=False + ): # pylint: disable=missing-docstring,no-self-use pass # pragma: no cover - def rollback_checkpoints(self, rollback=1): # pylint: disable=missing-docstring,no-self-use + def rollback_checkpoints( + self, rollback=1 + ): # pylint: disable=missing-docstring,no-self-use pass # pragma: no cover def recovery_routine(self): # pylint: disable=missing-docstring,no-self-use @@ -131,18 +154,25 @@ def config_test(self): # pylint: disable=missing-docstring,no-self-use def restart(self): # pylint: disable=missing-docstring,no-self-use pass # pragma: no cover - def renew_deploy(self, lineage, *args, **kwargs): # pylint: disable=missing-docstring,no-self-use + def renew_deploy( + self, lineage, *args, **kwargs + ): # pylint: disable=missing-docstring,no-self-use """ Renew certificates when calling `certbot renew` """ # Run deploy_cert with the lineage params - self.deploy_cert(lineage.names()[0], lineage.cert_path, lineage.key_path, lineage.chain_path, lineage.fullchain_path) + self.deploy_cert( + lineage.names()[0], + lineage.cert_path, + lineage.key_path, + lineage.chain_path, + lineage.fullchain_path, + ) return - class _AzureClient(object): """ Encapsulates all communication with the Azure Cloud DNS API. @@ -150,12 +180,12 @@ class _AzureClient(object): def __init__(self, resource_group, account_json=None): self.resource_group = resource_group - self.resource_client = get_client_from_auth_file(ResourceManagementClient, - auth_path=account_json) - self.network_client = get_client_from_auth_file(NetworkManagementClient, - auth_path=account_json) - - + self.resource_client = get_client_from_auth_file( + ResourceManagementClient, auth_path=account_json + ) + self.network_client = get_client_from_auth_file( + NetworkManagementClient, auth_path=account_json + ) def update_agw(self, agw_name, domain, key_path, fullchain_path): from azure.mgmt.network.models import ApplicationGatewaySslCertificate @@ -164,10 +194,14 @@ def update_agw(self, agw_name, domain, key_path, fullchain_path): password = token_urlsafe(16) # Get app gateway from client - agw = self.network_client.application_gateways.get(self.resource_group, agw_name) + agw = self.network_client.application_gateways.get( + self.resource_group, agw_name + ) if "Updating" in [ssl.provisioning_state for ssl in agw.ssl_certificates]: - raise errors.PluginError('There is a certificate in Updating state. Cowardly refusing to add a new one.') + raise errors.PluginError( + "There is a certificate in Updating state. Cowardly refusing to add a new one." + ) ssl = ApplicationGatewaySslCertificate() ssl.name = domain + str(int(time.time())) @@ -177,12 +211,14 @@ def update_agw(self, agw_name, domain, key_path, fullchain_path): agw.ssl_certificates.append(ssl) try: - self.network_client.application_gateways.create_or_update(self.resource_group, - agw_name, - agw) + self.network_client.application_gateways.create_or_update( + self.resource_group, agw_name, agw + ) except CloudError as e: - logger.warning('Encountered error updating app gateway: %s', e) - raise errors.PluginError('Error communicating with the Azure API: {0}'.format(e)) + logger.warning("Encountered error updating app gateway: %s", e) + raise errors.PluginError( + "Error communicating with the Azure API: {0}".format(e) + ) def _generate_pfx_from_pems(self, key_path, fullchain_path, password): """Generate PFX file out of PEMs in order to meet App Gateway requirements""" @@ -196,9 +232,7 @@ def _generate_pfx_from_pems(self, key_path, fullchain_path, password): # Load Key into PKCS12 object with open(key_path, "rb") as key_file: private_key = serialization.load_pem_private_key( - key_file.read(), - password=None, - backend=default_backend() + key_file.read(), password=None, backend=default_backend() ) key = OpenSSL.crypto.PKey.from_cryptography_key(private_key) @@ -207,8 +241,8 @@ def _generate_pfx_from_pems(self, key_path, fullchain_path, password): # Load Cert into PKCS12 object with open(fullchain_path, "rb") as cert_file: crypto_cert = x509.load_pem_x509_certificate( - cert_file.read(), - default_backend()) + cert_file.read(), default_backend() + ) cert = OpenSSL.crypto.X509.from_cryptography(crypto_cert) p12.set_certificate(cert) diff --git a/certbot_azure/azure_agw_test.py b/certbot_azure/azure_agw_test.py index 1489506..f4acea3 100644 --- a/certbot_azure/azure_agw_test.py +++ b/certbot_azure/azure_agw_test.py @@ -7,8 +7,6 @@ import json from certbot import errors -from certbot.plugins import dns_test_common_lexicon -from certbot.plugins.dns_test_common import DOMAIN from certbot.tests import util as test_util from requests import Response @@ -17,7 +15,8 @@ from azure.mgmt.network.models import ApplicationGatewaySslCertificate -RESOURCE_GROUP = 'test-test-1' +RESOURCE_GROUP = "test-test-1" + class AzureClientTest(test_util.TempDirTestCase): zone = "foo.com" @@ -38,6 +37,7 @@ def _generate_dummy_agw(self): def setUp(self): from certbot_azure.azure_agw import _AzureClient + super(AzureClientTest, self).setUp() config_path = AzureClientConfigDummy.build_config(self.tempdir) @@ -56,17 +56,17 @@ def test_update_agw(self): # pylint: disable=protected-access self.network_client.application_gateways.get.return_value = agw - self.azure_client.update_agw(agw.name, - "test_domain.com", - "test_key_path", - "test_cert_path") + self.azure_client.update_agw( + agw.name, "test_domain.com", "test_key_path", "test_cert_path" + ) self.network_client.application_gateways.create_or_update.assert_called_with( - self.azure_client.resource_group, - agw.name, - mock.ANY) + self.azure_client.resource_group, agw.name, mock.ANY + ) - updated_agw = self.network_client.application_gateways.create_or_update.call_args[0][2] + updated_agw = ( + self.network_client.application_gateways.create_or_update.call_args[0][2] + ) self.assertEqual(len(updated_agw.ssl_certificates), 1) @@ -74,27 +74,27 @@ def test_update_agw_error(self): agw = self._generate_dummy_agw() # pylint: disable=protected-access self.network_client.application_gateways.get.return_value = agw - self.network_client.application_gateways.create_or_update.side_effect = self._getCloudError() + self.network_client.application_gateways.create_or_update.side_effect = ( + self._getCloudError() + ) with self.assertRaises(errors.PluginError): - self.azure_client.update_agw(agw.name, - "test_domain.com", - "test_key_path", - "test_cert_path") + self.azure_client.update_agw( + agw.name, "test_domain.com", "test_key_path", "test_cert_path" + ) def test_update_agw_error_if_pending(self): agw = self._generate_dummy_agw() ssl = ApplicationGatewaySslCertificate() - ssl.provisioning_state = 'Updating' + ssl.provisioning_state = "Updating" agw.ssl_certificates = [ssl] # pylint: disable=protected-access self.network_client.application_gateways.get.return_value = agw with self.assertRaises(errors.PluginError): - self.azure_client.update_agw(agw.name, - "test_domain.com", - "test_key_path", - "test_cert_path") + self.azure_client.update_agw( + agw.name, "test_domain.com", "test_key_path", "test_cert_path" + ) class AzureClientConfigDummy(object): @@ -104,22 +104,26 @@ class AzureClientConfigDummy(object): def build_config(cls, tempdir): """Helper method to create dummy Azure configuration""" - config_path = os.path.join(tempdir, 'azurecreds.json') - with open(config_path, 'w') as outfile: - json.dump({ - "clientId": "uuid", - "clientSecret": "uuid", - "subscriptionId": "uuid", - "tenantId": "uuid", - "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", - "resourceManagerEndpointUrl": "https://management.azure.com/", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", - "galleryEndpointUrl": "https://gallery.azure.com/", - "managementEndpointUrl": "https://management.core.windows.net/" - }, outfile) + config_path = os.path.join(tempdir, "azurecreds.json") + with open(config_path, "w") as outfile: + json.dump( + { + "clientId": "uuid", + "clientSecret": "uuid", + "subscriptionId": "uuid", + "tenantId": "uuid", + "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", + "resourceManagerEndpointUrl": "https://management.azure.com/", + "activeDirectoryGraphResourceId": "https://graph.windows.net/", + "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", + "galleryEndpointUrl": "https://gallery.azure.com/", + "managementEndpointUrl": "https://management.core.windows.net/", + }, + outfile, + ) return config_path + if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot_azure/dns_azure.py b/certbot_azure/dns_azure.py index 54217f0..29ea846 100644 --- a/certbot_azure/dns_azure.py +++ b/certbot_azure/dns_azure.py @@ -1,11 +1,12 @@ """DNS Authenticator for Azure DNS.""" +import json import logging import os import zope.interface +from azure.identity import ClientSecretCredential from azure.mgmt.dns import DnsManagementClient -from azure.common.client_factory import get_client_from_auth_file from azure.mgmt.dns.models import RecordSet, TxtRecord from msrestazure.azure_exceptions import CloudError @@ -16,13 +17,18 @@ logger = logging.getLogger(__name__) -MSDOCS = 'https://docs.microsoft.com/' -ACCT_URL = MSDOCS + 'python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file' -AZURE_CLI_URL = MSDOCS + 'cli/azure/install-azure-cli?view=azure-cli-latest' -AZURE_CLI_COMMAND = ("az ad sp create-for-rbac" - " --name Certbot --sdk-auth --role \"DNS Zone Contributor\"" - " --scope /subscriptions//resourceGroups/ mycredentials.json") +MSDOCS = "https://docs.microsoft.com/" +ACCT_URL = ( + MSDOCS + + "python/azure/python-sdk-azure-authenticate?view=azure-python#mgmt-auth-file" +) +AZURE_CLI_URL = MSDOCS + "cli/azure/install-azure-cli?view=azure-cli-latest" +AZURE_CLI_COMMAND = ( + "az ad sp create-for-rbac" + ' --name Certbot --sdk-auth --role "DNS Zone Contributor"' + " --scope /subscriptions//resourceGroups/ mycredentials.json" +) @zope.interface.implementer(interfaces.IAuthenticator) @@ -34,8 +40,9 @@ class Authenticator(dns_common.DNSAuthenticator): """ description = ( - 'Obtain certificates using a DNS TXT record (if you are using Azure DNS ' - 'for DNS).') + "Obtain certificates using a DNS TXT record (if you are using Azure DNS " + "for DNS)." + ) ttl = 60 def __init__(self, *args, **kwargs): @@ -44,55 +51,62 @@ def __init__(self, *args, **kwargs): @classmethod def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments(add, - default_propagation_seconds=60) - add('credentials', + super(Authenticator, cls).add_parser_arguments( + add, default_propagation_seconds=60 + ) + add( + "credentials", help=( - 'Path to Azure DNS service account JSON file. If you already have a Service ' + - 'Principal with the required permissions, you can create your own file as per ' + - 'the JSON file format at {0}. ' + - 'Otherwise, you can create a new Service Principal using the Azure CLI ' + - '(available at {1}) by running "az login" then "{2}"' + - 'This will create file "mycredentials.json" which you should secure, then ' + - 'specify with this option or with the AZURE_AUTH_LOCATION environment variable.') - .format(ACCT_URL, AZURE_CLI_URL, AZURE_CLI_COMMAND), - default=None) - add('resource-group', - help=('Resource Group in which the DNS zone is located'), - default=None) + "Path to Azure DNS service account JSON file. If you already have a Service " + + "Principal with the required permissions, you can create your own file as per " + + "the JSON file format at {0}. " + + "Otherwise, you can create a new Service Principal using the Azure CLI " + + '(available at {1}) by running "az login" then "{2}"' + + 'This will create file "mycredentials.json" which you should secure, then ' + + "specify with this option or with the AZURE_AUTH_LOCATION environment variable." + ).format(ACCT_URL, AZURE_CLI_URL, AZURE_CLI_COMMAND), + default=None, + ) + add( + "resource-group", + help=("Resource Group in which the DNS zone is located"), + default=None, + ) def more_info(self): # pylint: disable=missing-docstring,no-self-use - return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \ - 'the Azure DNS API.' + return ( + "This plugin configures a DNS TXT record to respond to a dns-01 challenge using " + + "the Azure DNS API." + ) def _setup_credentials(self): - if self.conf('resource-group') is None: - raise errors.PluginError('Please specify a resource group using ' - '--dns-azure-resource-group ') + if self.conf("resource-group") is None: + raise errors.PluginError( + "Please specify a resource group using " + "--dns-azure-resource-group " + ) - if self.conf( - 'credentials') is None and 'AZURE_AUTH_LOCATION' not in os.environ: + if self.conf("credentials") is None and "AZURE_AUTH_LOCATION" not in os.environ: raise errors.PluginError( - 'Please specify credentials file using the ' - 'AZURE_AUTH_LOCATION environment variable or ' - 'using --dns-azure-credentials ') + "Please specify credentials file using the " + "AZURE_AUTH_LOCATION environment variable or " + "using --dns-azure-credentials " + ) else: - self._configure_file('credentials', - 'path to Azure DNS service account JSON file') + self._configure_file( + "credentials", "path to Azure DNS service account JSON file" + ) - dns_common.validate_file_permissions(self.conf('credentials')) + dns_common.validate_file_permissions(self.conf("credentials")) def _perform(self, domain, validation_name, validation): - self._get_azure_client().add_txt_record(validation_name, - validation, - self.ttl) + self._get_azure_client().add_txt_record(validation_name, validation, self.ttl) def _cleanup(self, domain, validation_name, validation): self._get_azure_client().del_txt_record(validation_name) def _get_azure_client(self): - return _AzureClient(self.conf('resource-group'), - self.conf('credentials')) + return _AzureClient(self.conf("resource-group"), self.conf("credentials")) class _AzureClient(object): @@ -102,8 +116,23 @@ class _AzureClient(object): def __init__(self, resource_group, account_json=None): self.resource_group = resource_group - self.dns_client = get_client_from_auth_file(DnsManagementClient, - auth_path=account_json) + with open(account_json) as json_file: + json_dict = json.load(json_file) + + credential = ClientSecretCredential( + tenant_id=json_dict["tenantId"], + client_id=json_dict["clientId"], + client_secret=json_dict["clientSecret"], + authority=json_dict["activeDirectoryEndpointUrl"], + ) + self.dns_client = DnsManagementClient( + credential, + json_dict["subscriptionId"], + base_url=json_dict["resourceManagerEndpointUrl"], + credential_scopes=[ + "{}/.default".format(json_dict["resourceManagerEndpointUrl"]) + ], + ) def add_txt_record(self, domain, record_content, record_ttl): """ @@ -115,19 +144,21 @@ def add_txt_record(self, domain, record_content, record_ttl): :raises certbot.errors.PluginError: if an error occurs communicating with the Azure API """ try: - record = RecordSet(ttl=record_ttl, - txt_records=[TxtRecord(value=[record_content])]) + record = RecordSet( + ttl=record_ttl, txt_records=[TxtRecord(value=[record_content])] + ) zone = self._find_managed_zone(domain) relative_record_name = ".".join( - domain.split('.')[0:-len(zone.split('.'))]) - self.dns_client.record_sets.create_or_update(self.resource_group, - zone, - relative_record_name, - 'TXT', - record) + domain.split(".")[0 : -len(zone.split("."))] + ) + self.dns_client.record_sets.create_or_update( + self.resource_group, zone, relative_record_name, "TXT", record + ) except CloudError as e: - logger.error('Encountered error adding TXT record: %s', e) - raise errors.PluginError('Error communicating with the Azure DNS API: {0}'.format(e)) + logger.error("Encountered error adding TXT record: %s", e) + raise errors.PluginError( + "Error communicating with the Azure DNS API: {0}".format(e) + ) def del_txt_record(self, domain): """ @@ -140,13 +171,13 @@ def del_txt_record(self, domain): try: zone = self._find_managed_zone(domain) relative_record_name = ".".join( - domain.split('.')[0:-len(zone.split('.'))]) - self.dns_client.record_sets.delete(self.resource_group, - zone, - relative_record_name, - 'TXT') + domain.split(".")[0 : -len(zone.split("."))] + ) + self.dns_client.record_sets.delete( + self.resource_group, zone, relative_record_name, "TXT" + ) except (CloudError, errors.PluginError) as e: - logger.warning('Encountered error deleting TXT record: %s', e) + logger.warning("Encountered error deleting TXT record: %s", e) def _find_managed_zone(self, domain): """ @@ -161,14 +192,16 @@ def _find_managed_zone(self, domain): azure_zones = self.dns_client.zones.list() # TODO - catch errors azure_zones_list = [] while True: - for zone in azure_zones.current_page: + for zone in azure_zones: azure_zones_list.append(zone.name) azure_zones.next() except StopIteration: pass except CloudError as e: - logger.error('Error finding zone: %s', e) - raise errors.PluginError('Error finding zone form the Azure DNS API: {0}'.format(e)) + logger.error("Error finding zone: %s", e) + raise errors.PluginError( + "Error finding zone form the Azure DNS API: {0}".format(e) + ) zone_dns_name_guesses = dns_common.base_domain_name_guesses(domain) for zone_name in zone_dns_name_guesses: @@ -176,5 +209,7 @@ def _find_managed_zone(self, domain): return zone_name raise errors.PluginError( - 'Unable to determine managed zone for {0} using zone names: {1}.' - .format(domain, zone_dns_name_guesses)) + "Unable to determine managed zone for {0} using zone names: {1}.".format( + domain, zone_dns_name_guesses + ) + ) diff --git a/certbot_azure/dns_azure_test.py b/certbot_azure/dns_azure_test.py index 9765225..8b13657 100644 --- a/certbot_azure/dns_azure_test.py +++ b/certbot_azure/dns_azure_test.py @@ -15,12 +15,12 @@ from msrestazure.azure_exceptions import CloudError -RESOURCE_GROUP = 'test-test-1' +RESOURCE_GROUP = "test-test-1" -class AuthenticatorTest(test_util.TempDirTestCase, - dns_test_common_lexicon.BaseLexiconAuthenticatorTest): - +class AuthenticatorTest( + test_util.TempDirTestCase, dns_test_common_lexicon.BaseLexiconAuthenticatorTest +): def setUp(self): from certbot_azure.dns_azure import Authenticator @@ -28,8 +28,9 @@ def setUp(self): config_path = AzureClientConfigDummy.build_config(self.tempdir) - self.config = mock.MagicMock(azure_credentials=config_path, - azure_resource_group=RESOURCE_GROUP) + self.config = mock.MagicMock( + azure_credentials=config_path, azure_resource_group=RESOURCE_GROUP + ) self.auth = Authenticator(self.config, "azure") @@ -40,7 +41,9 @@ def setUp(self): def test_perform(self): self.auth.perform([self.achall]) - expected = [mock.call.add_txt_record('_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)] + expected = [ + mock.call.add_txt_record("_acme-challenge." + DOMAIN, mock.ANY, mock.ANY) + ] self.assertEqual(expected, self.mock_client.mock_calls) def test_cleanup(self): @@ -48,7 +51,7 @@ def test_cleanup(self): self.auth._attempt_cleanup = True self.auth.cleanup([self.achall]) - expected = [mock.call.del_txt_record('_acme-challenge.'+DOMAIN)] + expected = [mock.call.del_txt_record("_acme-challenge." + DOMAIN)] self.assertEqual(expected, self.mock_client.mock_calls) @@ -65,6 +68,7 @@ def _getCloudError(self): def setUp(self): from certbot_azure.dns_azure import _AzureClient + super(AzureClientTest, self).setUp() config_path = AzureClientConfigDummy.build_config(self.tempdir) @@ -80,16 +84,17 @@ def test_add_txt_record(self): # pylint: disable=protected-access self.azure_client._find_managed_zone.return_value = self.zone - self.azure_client.add_txt_record(self.record_name + "." + self.zone, - self.record_content, - self.record_ttl) + self.azure_client.add_txt_record( + self.record_name + "." + self.zone, self.record_content, self.record_ttl + ) self.dns_client.record_sets.create_or_update.assert_called_with( - self.azure_client.resource_group, - self.zone, - self.record_name, - 'TXT', - mock.ANY) + self.azure_client.resource_group, + self.zone, + self.record_name, + "TXT", + mock.ANY, + ) record = self.dns_client.record_sets.create_or_update.call_args[0][4] @@ -103,9 +108,9 @@ def test_add_txt_record_error(self): self.dns_client.record_sets.create_or_update.side_effect = self._getCloudError() with self.assertRaises(errors.PluginError): - self.azure_client.add_txt_record(self.record_name + "." + self.zone, - self.record_content, - self.record_ttl) + self.azure_client.add_txt_record( + self.record_name + "." + self.zone, self.record_content, self.record_ttl + ) def test_add_txt_record_zone_not_found(self): # pylint: disable=protected-access @@ -114,9 +119,9 @@ def test_add_txt_record_zone_not_found(self): self.azure_client._find_managed_zone.side_effect = self._getCloudError() with self.assertRaises(errors.PluginError): - self.azure_client.add_txt_record(self.record_name + "." + self.zone, - self.record_content, - self.record_ttl) + self.azure_client.add_txt_record( + self.record_name + "." + self.zone, self.record_content, self.record_ttl + ) def test_del_txt_record(self): # pylint: disable=protected-access @@ -124,10 +129,10 @@ def test_del_txt_record(self): self.azure_client.del_txt_record(self.record_name + "." + self.zone) - self.dns_client.record_sets.delete.assert_called_with(self.azure_client.resource_group, - self.zone, - self.record_name, - 'TXT') + self.dns_client.record_sets.delete.assert_called_with( + self.azure_client.resource_group, self.zone, self.record_name, "TXT" + ) + def test_del_txt_record_no_zone(self): # pylint: disable=protected-access self.azure_client._find_managed_zone.return_value = None @@ -146,24 +151,28 @@ class AzureClientConfigDummy(object): def build_config(cls, tempdir): """Helper method to create dummy Azure configuration""" - config_path = os.path.join(tempdir, 'azurecreds.json') - with open(config_path, 'w') as outfile: - json.dump({ - "clientId": "uuid", - "clientSecret": "uuid", - "subscriptionId": "uuid", - "tenantId": "uuid", - "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", - "resourceManagerEndpointUrl": "https://management.azure.com/", - "activeDirectoryGraphResourceId": "https://graph.windows.net/", - "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", - "galleryEndpointUrl": "https://gallery.azure.com/", - "managementEndpointUrl": "https://management.core.windows.net/" - }, outfile) + config_path = os.path.join(tempdir, "azurecreds.json") + with open(config_path, "w") as outfile: + json.dump( + { + "clientId": "uuid", + "clientSecret": "uuid", + "subscriptionId": "uuid", + "tenantId": "uuid", + "activeDirectoryEndpointUrl": "https://login.microsoftonline.com", + "resourceManagerEndpointUrl": "https://management.azure.com/", + "activeDirectoryGraphResourceId": "https://graph.windows.net/", + "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/", + "galleryEndpointUrl": "https://gallery.azure.com/", + "managementEndpointUrl": "https://management.core.windows.net/", + }, + outfile, + ) os.chmod(config_path, 0o600) return config_path + if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/requirements.txt b/requirements.txt index 47338d7..16de180 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,15 @@ certbot==1.6.0 -adal==1.2.4 -azure-common==1.1.25 +azure-identity==1.10.0 azure-mgmt-network==11.0.0 azure-mgmt-resource==10.1.0 azure-mgmt-dns>=3.0.0 configobj==5.0.6 coverage==5.2.1 -cryptography==3.2 +cryptography==3.3.2 decorator==4.4.2 docutils==0.16 entrypoints==0.3 -httplib2==0.18.1 +httplib2==0.19.0 logger==1.4 mock==4.0.2 msrest==0.6.18 diff --git a/setup.cfg b/setup.cfg index b88034e..9c84f7f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,8 @@ [metadata] description-file = README.md +[flake8] +max-line-length = 120 +max-complexity = 10 +ignore = + E203 # [psf/black#280](https://github.com/psf/black/issues/280) + W503 # https://github.com/psf/black/issues/52 \ No newline at end of file diff --git a/setup.py b/setup.py index 1ca2011..7178907 100644 --- a/setup.py +++ b/setup.py @@ -3,61 +3,63 @@ from distutils.core import setup from setuptools import find_packages -version = '0.1.0' +version = "0.2.0" install_requires = [ - 'acme>=0.29.0', - 'certbot>=1.1.0', - 'azure-mgmt-resource', - 'azure-mgmt-network', - 'azure-mgmt-dns>=3.0.0', - 'PyOpenSSL>=19.1.0', - 'setuptools', # pkg_resources - 'zope.interface' + "acme>=0.29.0", + "certbot>=1.1.0", + "msrestazure", + "azure-identity", + "azure-mgmt-resource", + "azure-mgmt-network", + "azure-mgmt-dns>=3.0.0", + "PyOpenSSL>=19.1.0", + "setuptools", # pkg_resources + "zope.interface", ] if sys.version_info < (2, 7): - install_requires.append('mock<1.1.0') + install_requires.append("mock<1.1.0") else: - install_requires.append('mock') + install_requires.append("mock") docs_extras = [ - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', + "Sphinx>=1.0", # autodoc_member_order = 'bysource', autodoc_default_flags + "sphinx_rtd_theme", ] setup( - name='certbot-azure', + name="certbot-azure", version=version, description="Azure plugin for Certbot client", - url='https://github.com/dlapiduz/certbot-azure', + url="https://github.com/dlapiduz/certbot-azure", author="Diego Lapiduz", - author_email='diego@lapiduz.com', - license='MIT', + author_email="diego@lapiduz.com", + license="MIT", classifiers=[ - 'Development Status :: 3 - Alpha', - 'Environment :: Plugins', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Security', - 'Topic :: System :: Installation/Setup', - 'Topic :: System :: Networking', - 'Topic :: System :: Systems Administration', - 'Topic :: Utilities', + "Development Status :: 3 - Alpha", + "Environment :: Plugins", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: Apache Software License", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Security", + "Topic :: System :: Installation/Setup", + "Topic :: System :: Networking", + "Topic :: System :: Systems Administration", + "Topic :: Utilities", ], packages=find_packages(), include_package_data=True, install_requires=install_requires, - keywords = ['certbot', 'azure', 'app_gateway', 'azure_dns'], + keywords=["certbot", "azure", "app_gateway", "azure_dns"], entry_points={ - 'certbot.plugins': [ - 'azure-agw = certbot_azure.azure_agw:Installer', - 'dns-azure = certbot_azure.dns_azure:Authenticator', + "certbot.plugins": [ + "azure-agw = certbot_azure.azure_agw:Installer", + "dns-azure = certbot_azure.dns_azure:Authenticator", ], }, )