Skip to content

Commit

Permalink
Merge branch 'master' into chore/deprecate-support-for-python-3.8
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredhendrickson13 authored Nov 5, 2024
2 parents 3ba8c3c + 08d2870 commit e06b1f5
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 13 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,18 @@ jobs:
python3 -m pip install .
python3 -m pip install -r requirements-dev.txt
- name: Install Docker Compose
run: |
curl -L "https://github.com/docker/compose/releases/download/$(curl -s https://api.github.com/repos/docker/compose/releases/latest | jq -r '.tag_name')/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version
- name: Start Pebble server
run: docker-compose -f simple_acme_dns/tests/docker-compose.yml up -d

- name: Test simple_acme_dns package to ensure 100% coverage
env:
ACME_DIRECTORY: 'https://localhost:14000/dir'
run: |
python3 -m coverage run --module unittest simple_acme_dns/tests/test_*
python3 -m coverage report --show-missing --fail-under 100
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ exclude-too-few-public-methods=
ignored-parents=

# Maximum number of arguments for function / method.
max-args=7
max-args=8

# Maximum number of attributes for a class (see R0902).
max-attributes=22
Expand Down
19 changes: 14 additions & 5 deletions simple_acme_dns/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from . import errors
from . import tools


# Constants and Variables
DNS_LABEL = '_acme-challenge'
__pdoc__ = {"tests": False} # Excludes 'tests' submodule from documentation
Expand All @@ -54,7 +55,8 @@ def __init__(
directory: str = "https://acme-staging-v02.api.letsencrypt.org/directory",
nameservers: list = None,
new_account: bool = False,
generate_csr: bool = False
generate_csr: bool = False,
verify_ssl: bool = True
):
"""
Args:
Expand All @@ -64,6 +66,8 @@ def __init__(
nameservers (list): A list of DNS server hosts to query when checking DNS propagation.
new_account (bool): Automatically create a new ACME account upon creation.
generate_csr (bool): Automatically generate a new private key and CSR upon creation.
verify_ssl (bool): Verify the SSL certificate of the ACME server when making requests. This only applies
when creating a new account.
Examples:
>>> import simple_acme_dns
Expand Down Expand Up @@ -97,7 +101,7 @@ def __init__(

# Automatically create a new account if requested
if new_account:
self.new_account()
self.new_account(verify_ssl=verify_ssl)
# Automatically create a new private key and CSR
if generate_csr:
self.generate_private_key_and_csr()
Expand Down Expand Up @@ -281,11 +285,14 @@ def revoke_certificate(self, reason: int = 0) -> None:
cert_obj = jose.ComparableX509(OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, self.certificate))
self.acme_client.revoke(cert_obj, reason)

def new_account(self) -> None:
def new_account(self, verify_ssl=True) -> None:
"""
Registers a new ACME account at the set ACME `directory` URL. By running this method, you are agreeing to the
ACME servers terms of use.
Args:
verify_ssl (bool): Verify the SSL certificate of the ACME server when making requests.
Examples:
>>> client.new_account()
"""
Expand All @@ -294,7 +301,7 @@ def new_account(self) -> None:
self.account_key = jose.JWKRSA(key=rsa_key)

# Initialize our ACME client object
self.net = client.ClientNetwork(self.account_key, user_agent='simple_acme_dns/v2')
self.net = client.ClientNetwork(self.account_key, user_agent='simple_acme_dns/v2', verify_ssl=verify_ssl)
self.directory_obj = messages.Directory.from_json(self.net.get(self.directory).json())
self.acme_client = client.ClientV2(self.directory_obj, net=self.net)

Expand Down Expand Up @@ -346,6 +353,7 @@ def export_account(self, save_certificate: bool = True, save_private_key: bool =
'account': self.account.to_json(),
'account_key': self.account_key.json_dumps(),
'directory': self.directory,
'verify_ssl': self.net.verify_ssl,
'domains': self._domains,
'certificate': self.certificate.decode() if save_certificate else '',
'private_key': self.private_key.decode() if save_private_key else ''
Expand Down Expand Up @@ -412,6 +420,7 @@ def load_account(json_data: str) -> 'ACMEClient':
obj = ACMEClient()

# Format the serialized data back into the object
verify_ssl = acct_data.get('verify_ssl', True)
obj.directory = acct_data.get('directory', None)
obj.domains = acct_data.get('domains', [])
obj.certificate = acct_data.get('certificate', '').encode()
Expand All @@ -422,7 +431,7 @@ def load_account(json_data: str) -> 'ACMEClient':
obj.account_key = jose.JWKRSA.json_loads(acct_data['account_key'])

# Re-initialize the ACME client and registration
obj.net = client.ClientNetwork(obj.account_key, user_agent='simple_acme_dns/1.0.0')
obj.net = client.ClientNetwork(obj.account_key, user_agent='simple_acme_dns/1.0.0', verify_ssl=verify_ssl)
obj.directory_obj = messages.Directory.from_json(obj.net.get(obj.directory).json())
obj.acme_client = client.ClientV2(obj.directory_obj, net=obj.net)
obj.account = obj.acme_client.query_registration(obj.account)
Expand Down
27 changes: 27 additions & 0 deletions simple_acme_dns/tests/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Docker compose file for setting up a Pebble ACME and challenge server for testing
version: "3"
services:
pebble:
image: ghcr.io/letsencrypt/pebble:latest
command: -config test/config/pebble-config.json -strict -dnsserver 1.1.1.1:53
ports:
- '14000:14000' # HTTPS ACME API
- '15000:15000' # HTTPS Management API
networks:
acmenet:
ipv4_address: 10.30.50.2
challtestsrv:
image: ghcr.io/letsencrypt/pebble-challtestsrv:latest
command: -defaultIPv6 "" -defaultIPv4 10.30.50.3
ports:
- 8055:8055 # HTTP Management API
networks:
acmenet:
ipv4_address: 10.30.50.3
networks:
acmenet:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.30.50.0/24
20 changes: 13 additions & 7 deletions simple_acme_dns/tests/test_simple_acme_dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""Tests primary functionality of the simple_acme_dns package."""
import os
import random
import time
import unittest

import acme.messages
Expand All @@ -25,7 +26,7 @@
BASE_DOMAIN = "testing.jaredhendrickson.com"
TEST_DOMAINS = [f"{random.randint(10000, 99999)}.simple-acme-dns.{BASE_DOMAIN}"]
TEST_EMAIL = f"simple-acme-dns@{BASE_DOMAIN}"
TEST_DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory"
TEST_DIRECTORY = os.environ.get("ACME_DIRECTORY", "https://acme-staging-v02.api.letsencrypt.org/directory")
TEST_NAMESERVERS = ["8.8.8.8", "1.1.1.1"]
unittest.TestLoader.sortTestMethodsUsing = None # Ensure tests run in order

Expand All @@ -42,7 +43,8 @@ def setUpClass(cls):
domains=TEST_DOMAINS,
email=TEST_EMAIL,
directory=TEST_DIRECTORY,
nameservers=TEST_NAMESERVERS
nameservers=TEST_NAMESERVERS,
verify_ssl=False
)

@classmethod
Expand Down Expand Up @@ -77,7 +79,7 @@ def test_generate_keys_and_csr(self):

def test_account_enrollment(self):
"""Test to ensure ACME accounts are successfully registered."""
self.client.new_account()
self.client.new_account(verify_ssl=False)
self.assertIsNotNone(self.client.account)
self.assertIsNotNone(self.client.account_key)
self.assertTrue(is_json(self.client.export_account()))
Expand All @@ -90,7 +92,8 @@ def test_shorthand_enrollment_and_csr(self):
directory=TEST_DIRECTORY,
nameservers=TEST_NAMESERVERS,
new_account=True,
generate_csr=True
generate_csr=True,
verify_ssl=False
)

# Ensure there is an account enrolled and a CSR/private key created
Expand All @@ -111,7 +114,7 @@ def test_acme_verification_and_cert(self):
"""Test to ensure ACME verification works and a certificate is obtained."""
# Request verification tokens
self.client.generate_private_key_and_csr()
self.client.new_account()
self.client.new_account(verify_ssl=False)
self.assertIsInstance(self.client.request_verification_tokens(), dict)

# Before we actually create the DNS entries, ensure DNS propagation checks fail as expected
Expand All @@ -133,6 +136,7 @@ def test_acme_verification_and_cert(self):
)

# Request the certificates and ensure a certificate is received
time.sleep(15) # Wait for DNS to propagate
self.assertTrue(is_cert(self.client.request_certificate()))

def test_account_exports(self):
Expand Down Expand Up @@ -189,7 +193,8 @@ def test_delete_account_file(self):
directory=TEST_DIRECTORY,
nameservers=TEST_NAMESERVERS,
new_account=True,
generate_csr=True
generate_csr=True,
verify_ssl=False
)
client.account_path = "INVALID_FILE.json"
self.assertIsNone(client.deactivate_account(delete=True))
Expand All @@ -205,7 +210,8 @@ def test_email_property_when_email_is_not_set(self):
client = simple_acme_dns.ACMEClient(
domains=TEST_DOMAINS,
directory=TEST_DIRECTORY,
nameservers=TEST_NAMESERVERS
nameservers=TEST_NAMESERVERS,
verify_ssl=False
)
return client.email

Expand Down

0 comments on commit e06b1f5

Please sign in to comment.