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

Add more aliasing support #34

Merged
merged 7 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
53 changes: 44 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Release
on:
push:
branches:
- master
- "*"
tags:
- "*"

Expand All @@ -13,35 +13,70 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: 3.11
- name: Install setuptools and
run: |
python -m pip install --upgrade setuptools wheel
python -m pip install -r requirements-dev.txt
- name: Test
run: pytest tests/
run: pytest -rA tests/

azure_test:
name: Azure Test
runs-on: ubuntu-latest
environment:
name: dev.azure
# Not using OIDC Auth
#permissions:
# id-token: write
# contents: read
steps:
# Not using CLI Auth
#- name: 'Az CLI Login via OIDC'
# uses: azure/login@v1
# with:
# client-id: ${{ secrets.AZURE_CLIENT_ID }}
# tenant-id: ${{ secrets.AZURE_TENANT_ID }}
# subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: 3.11
- name: Install setuptools and
run: |
python -m pip install --upgrade setuptools wheel
python -m pip install -r requirements-dev.txt
- name: Test
run: pytest -rA azure_tests/
env:
EMAIL: ${{ vars.EMAIL }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}

publish:
name: Publish
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
needs: test
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: 3.11
- name: Install setuptools and
run: python -m pip install --upgrade setuptools wheel
- name: Build a binary wheel and a source tarball
run: python setup.py build sdist bdist_wheel

- name: Publish distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_TOKEN }}
#with:
# password: ${{ secrets.PYPI_TOKEN }}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Python Version](https://img.shields.io/pypi/pyversions/certbot-dns-azure)](https://pypi.org/project/certbot-dns-azure/)
[![PyPi Status](https://img.shields.io/pypi/status/certbot-dns-azure)](https://pypi.org/project/certbot-dns-azure/)
[![Version](https://img.shields.io/pypi/v/certbot-dns-azure)](https://pypi.org/project/certbot-dns-azure/)
[![Docs](https://readthedocs.org/projects/certbot-dns-azure/badge/?version=latest&style=flat)](https://certbot-dns-azure.readthedocs.io/en/latest/)
[![Docs](https://readthedocs.org/projects/certbot-dns-azure/badge/?version=latest&style=flat)](https://docs.certbot-dns-azure.co.uk/en/latest/)

AzureDNS Authenticator plugin for [Certbot](https://certbot.eff.org/).

Expand Down Expand Up @@ -48,6 +48,6 @@ Entry point: dns-azure = certbot_dns_azure.dns_azure:Authenticator
...
```

Docs and instructions on configuration are [here](https://certbot-dns-azure.readthedocs.io/en/latest/)
Docs and instructions on configuration are [here](https://docs.certbot-dns-azure.co.uk/en/latest/)


208 changes: 208 additions & 0 deletions azure_tests/integration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import os
import subprocess
import uuid
from typing import TYPE_CHECKING, List, Tuple

import pytest
from azure.mgmt.dns import DnsManagementClient
from azure.identity import ClientSecretCredential, AzureCliCredential

if TYPE_CHECKING:
import pathlib

AZURE_ENV = os.getenv("AZURE_ENVIRONMENT", "AzurePublicCloud")
EMAIL = os.getenv('EMAIL', 'NOT_AN_EMAIL')

azure_creds = pytest.mark.skipif(
any(env not in os.environ for env in ['AZURE_CLIENT_ID', 'AZURE_TENANT_ID', 'EMAIL']),
reason="Missing 'AZURE_CLIENT_ID', 'AZURE_TENANT_ID' environment variables"
)

SUBSCRIPTION_ID = '90907259-f568-40c9-be09-768317e458ae'
RESOURCE_GROUP = 'certbot'

ZONES = {
'zone1.certbot-dns-azure.co.uk': '/subscriptions/90907259-f568-40c9-be09-768317e458ae/resourceGroups/certbot', # /providers/Microsoft.Network/dnszones/zone1.certbot-dns-azure.co.uk
'zone2.certbot-dns-azure.co.uk': '/subscriptions/90907259-f568-40c9-be09-768317e458ae/resourceGroups/certbot', # providers/Microsoft.Network/dnszones/zone2.certbot-dns-azure.co.uk
'del2.certbot-dns-azure.co.uk': '/subscriptions/90907259-f568-40c9-be09-768317e458ae/resourceGroups/certbot',
}
DELEGATION_ZONE = '/subscriptions/90907259-f568-40c9-be09-768317e458ae/resourceGroups/certbot/providers/Microsoft.Network/dnsZones/del2.certbot-dns-azure.co.uk'
DELEGATION_ZONE2 = '/subscriptions/90907259-f568-40c9-be09-768317e458ae/resourceGroups/certbot/providers/Microsoft.Network/dnsZones/del1.certbot-dns-azure.co.uk/TXT/other'


def get_cert_names(count: int = 1) -> List[str]:
return [uuid.uuid4().hex for _ in range(count)]


@pytest.fixture(scope='session')
def azure_dns_client() -> DnsManagementClient:
if 'AZURE_CLIENT_SECRET' in os.environ:
creds = ClientSecretCredential(
client_id=os.environ['AZURE_CLIENT_ID'],
client_secret=os.environ['AZURE_CLIENT_SECRET'],
tenant_id=os.environ['AZURE_TENANT_ID'],
authority='https://login.microsoftonline.com/'
)
else:
creds = AzureCliCredential(tenant_id=os.environ['AZURE_TENANT_ID'])
return DnsManagementClient(creds, SUBSCRIPTION_ID, None, 'https://management.azure.com/', credential_scopes=['https://management.azure.com//.default'])


@pytest.fixture(scope='function', autouse=True)
def cleanup_dns(azure_dns_client):
"""
Cleans up all records in all zones defined in ZONES

:param azure_dns_client: pytest dns client fixture
"""
yield

for zone in ZONES:
to_delete = []
for rr in azure_dns_client.record_sets.list_by_dns_zone(RESOURCE_GROUP, zone):
rr_type = rr.type.rsplit('/', 1)[-1]
if rr_type in ('NS', 'SOA'):
continue

to_delete.append((rr.name, rr_type))
for rr_name, rr_type in to_delete:
try:
azure_dns_client.record_sets.delete(RESOURCE_GROUP, zone, rr_name, rr_type)
print(f"Deleted {zone}/{rr_name}")
except Exception as err:
print(f"Tried to delete {zone}/{rr_name}, got: {err}")


def create_config(tmpdir: 'pathlib.Path', zones: List[str]) -> str:
"""
Creates a config file for certbot azure dns

:param tmpdir: Temporary pytest fixture
:param zones: List of zone entries for config
:returns: Filepath to config
"""
config = {
'dns_azure_sp_client_id': os.environ['AZURE_CLIENT_ID'],
'dns_azure_sp_client_secret': os.environ['AZURE_CLIENT_SECRET'],
'dns_azure_tenant_id': os.environ['AZURE_TENANT_ID'],
'dns_azure_environment': AZURE_ENV,
}
for index, zone in enumerate(zones, start=1):
config[f"dns_azure_zone{index}"] = zone

config_text = '\n'.join([' = '.join(item) for item in config.items()]) + '\n'
config_file = tmpdir / "config.ini"
config_file.write_text(config_text)
config_file.chmod(0o600)
return str(config_file)


def run_certbot(certbot_path: 'pathlib.Path', config_file: str, fqdns: List[str], *, dry_run: bool = False) -> Tuple[subprocess.Popen, str, str]:
args = [
'certbot', 'certonly', '--authenticator', 'dns-azure', '--preferred-challenges', 'dns', '--noninteractive',
'--agree-tos',
'--email', EMAIL,
'--config-dir', certbot_path, '--work-dir', certbot_path, '--logs-dir', certbot_path,
'--dns-azure-config', config_file,
]
if dry_run:
args.append('--dry-run')
for fqdn in fqdns:
args.extend(['-d', fqdn])

proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
stdout, stderr = proc.communicate()
if proc.returncode != 0:
print(f"Error, return code {proc.returncode}\nSTDERR:\n{stderr}\nSTDOUT:\n{stdout}")
pytest.fail()

return proc, stdout, stderr


@azure_creds
def test_single_zone(tmp_path, azure_dns_client):
"""
Tests getting a certificate for a single zone
"""
certbot_path = tmp_path / "certbot"
zone = 'zone1.certbot-dns-azure.co.uk'
rr_name = get_cert_names(1)[0]
fqdn = f"{rr_name}.{zone}"

zone_entry = f"{zone}:{ZONES[zone]}"
config_file = create_config(tmp_path, [zone_entry])

proc, stdout, stderr = run_certbot(certbot_path, config_file, [fqdn])

cert_path = certbot_path / 'archive' / fqdn / 'cert1.pem'
if not cert_path.exists():
print(f"STDOUT:\n{stdout}")
pytest.fail(f"Certificate path {cert_path} does not exist")


@azure_creds
def test_multi_zone(tmp_path, azure_dns_client):
"""
Tests getting a certificate for multiple zones
"""
certbot_path = tmp_path / "certbot"
zone1 = 'zone1.certbot-dns-azure.co.uk'
zone2 = 'zone2.certbot-dns-azure.co.uk'

rr_name1, rr_name2 = get_cert_names(2)
fqdn1 = f"{rr_name1}.{zone1}"
fqdn2 = f"{rr_name2}.{zone2}"

zone_entry1 = f"{zone1}:{ZONES[zone1]}"
zone_entry2 = f"{zone2}:{ZONES[zone2]}"
config_file = create_config(tmp_path, [zone_entry1, zone_entry2])

proc, stdout, stderr = run_certbot(certbot_path, config_file, [fqdn1, fqdn2])

cert_path1 = certbot_path / 'archive' / fqdn1 / 'cert1.pem'
cert_path2 = certbot_path / 'archive' / fqdn2 / 'cert1.pem'
if not cert_path1.exists() and not cert_path2.exists():
print(f"STDOUT:\n{stdout}")
pytest.fail(f"Certificate path {cert_path1} or {cert_path2} does not exist")


@azure_creds
def test_delegation_other_domain(tmp_path, azure_dns_client):
"""
Tests getting a certificate for a single zone
"""
certbot_path = tmp_path / "certbot"
fqdn = 'del1.certbot-dns-azure.co.uk'

# domain is del1, but we're explicitly overriding the zone to del2
config_file = create_config(tmp_path, [
f"{fqdn}:{DELEGATION_ZONE}"
])

proc, stdout, stderr = run_certbot(certbot_path, config_file, [fqdn])

cert_path = certbot_path / 'archive' / fqdn / 'cert1.pem'
if not cert_path.exists():
print(f"STDOUT:\n{stdout}")
pytest.fail(f"Certificate path {cert_path} does not exist")


@azure_creds
def test_delegation_specific_record(tmp_path, azure_dns_client):
"""
Tests getting a certificate for a single zone
"""
certbot_path = tmp_path / "certbot"
fqdn = 'test.del1.certbot-dns-azure.co.uk'

# domain is del1, but we're explicitly overriding to an alternate record of del1
config_file = create_config(tmp_path, [
f"{fqdn}:{DELEGATION_ZONE2}"
])

proc, stdout, stderr = run_certbot(certbot_path, config_file, [fqdn])

cert_path = certbot_path / 'archive' / fqdn / 'cert1.pem'
if not cert_path.exists():
print(f"STDOUT:\n{stdout}")
pytest.fail(f"Certificate path {cert_path} does not exist")
Loading