diff --git a/README.md b/README.md index df42739..8012b6f 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,32 @@ certbot certonly -d REPLACE_WITH_YOUR_DOMAIN \ Follow the screen prompts and you should end up with the certificate in your distribution. It may take a couple minutes to update. +### Obtaining a certificate with Azure Managed identity authentication + +Create a managed identity +```bash +az identity create -g -n +``` +Assign it the DNS Zone Contributor role +```bash +az role assignment create --assignee-object-id \ +--role "DNS Zone Contributor" +``` + +Assign the managed identity to a virtual machine + +Then generate the certificate: + +```bash +certbot certonly -d REPLACE_WITH_YOUR_DOMAIN \ +-a dns-azure --dns-azure-resource-group \ +--dns-azure-managedidentity-subscription= \ +--dns-azure-managedidentity-clientid= +``` + +If the `--dns-azure-managedidentity-clientid` argument is omitted, then the +system assigned identity will be used instead of a user assigned one. + ### Installing a certificate to an Azure App Gateway diff --git a/certbot_azure/dns_azure.py b/certbot_azure/dns_azure.py index 54217f0..75fa037 100644 --- a/certbot_azure/dns_azure.py +++ b/certbot_azure/dns_azure.py @@ -8,6 +8,7 @@ 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 +from msrestazure.azure_active_directory import MSIAuthentication from certbot import errors @@ -61,6 +62,11 @@ def add_parser_arguments(cls, add): # pylint: disable=arguments-differ help=('Resource Group in which the DNS zone is located'), default=None) + add('managedidentity-subscription', help='DNS Zone subscription id', + default=None), + add('managedidentity-clientid', help='Use a user-assigned managed identity instead of system assigned', + 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.' @@ -71,16 +77,23 @@ def _setup_credentials(self): '--dns-azure-resource-group ') if self.conf( - 'credentials') is None and 'AZURE_AUTH_LOCATION' not in os.environ: + 'credentials') is None and 'AZURE_AUTH_LOCATION' not in os.environ and self.conf( + 'managedidentity-subscription') is None and \ + self.conf('managedidentity-clientid') is None: raise errors.PluginError( 'Please specify credentials file using the ' 'AZURE_AUTH_LOCATION environment variable or ' - 'using --dns-azure-credentials ') + 'using --dns-azure-credentials . To use a ' + 'system assigned managed identity use ' + '--dns-azure-managedidentity-subscription=sub, or add ' + '--dns-azure-managedidentity-clientid=clientid' + 'to specify a user assigned managed identity') else: - self._configure_file('credentials', - 'path to Azure DNS service account JSON file') + if self.conf('credentials') is not None or 'AZURE_AUTH_LOCATION' in os.environ: + 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, @@ -92,7 +105,9 @@ def _cleanup(self, domain, validation_name, validation): def _get_azure_client(self): return _AzureClient(self.conf('resource-group'), - self.conf('credentials')) + self.conf('credentials'), + self.conf('managedidentity-subscription'), + self.conf('managedidentity-clientid')) class _AzureClient(object): @@ -100,10 +115,16 @@ class _AzureClient(object): Encapsulates all communication with the Azure Cloud DNS API. """ - def __init__(self, resource_group, account_json=None): + def __init__(self, resource_group, account_json=None, mi_subscription=None, mi_clientid=None): self.resource_group = resource_group - self.dns_client = get_client_from_auth_file(DnsManagementClient, - auth_path=account_json) + + if mi_subscription and mi_clientid: + self.dns_client = DnsManagementClient(MSIAuthentication(client_id=mi_clientid), mi_subscription) + elif mi_subscription: + self.dns_client = DnsManagementClient(MSIAuthentication(), mi_subscription) + else: + self.dns_client = get_client_from_auth_file(DnsManagementClient, + auth_path=account_json) def add_txt_record(self, domain, record_content, record_ttl): """