-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
056ac64
commit 557114b
Showing
8 changed files
with
305 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# windns - Windows DNSServer module | ||
|
||
windns driver uses PowerShell DNSServer module for Let's Encrypt dns challenge. | ||
|
||
Before reading further, [check the following link](https://docs.microsoft.com/en-us/powershell/module/dnsserver) to | ||
understand what this driver offers and how it works. | ||
|
||
The driver uses a wrapper python library, which is called | ||
[windowsdnsserver-py](https://github.com/bilalekremharmansa/windowsdnsserver-py), to interact with PowerShell DnsServer | ||
module. Basically, the driver performs process calls to DNSServer over python subprocess module. Since commands are | ||
made on local machine (remote session is not supported), this driver has to be used on windows server | ||
where dns server is located, either using sewer as a cli and as a library. | ||
|
||
# Installation | ||
|
||
DNSServer has any requirements but DNSServer module, which must be installed to PowerShell on windows server. | ||
|
||
Microsoft documentation: | ||
> DnsServer Module can be obtained either by installing DNS Server role or adding the DNS Server Tools part of | ||
> Remote Server Administration Tools (RSAT) feature. | ||
## Usage | ||
|
||
windns driver needs to know which dns zone to put TXT record for dns challenge because DNSServer module | ||
expects dns zone and dns name respectively. Nevertheless, domain should be provided to sewer as other dns drivers. | ||
|
||
For instance, to start a dns challenge to "test.example.com" at zone "example.com", domain and zone should be defined | ||
below; | ||
|
||
domain = test.example.com | ||
zone = example.com | ||
|
||
If zone and domain are exactly same (both example.com), following definition work as well; | ||
|
||
domain = example.com | ||
zone = example.com | ||
|
||
### sewer-cli | ||
|
||
python3 -m sewer ... --provider=windns --p_opt zone=example.com | ||
|
||
### sewer as library | ||
zone = "example.com" | ||
provider = WinDNS(zone) | ||
client = client.Client(domain="...", provider=provider, ...) | ||
|
||
|
||
### Overriding default PowerShell path | ||
|
||
windowsdnsserver-py uses the following PowerShell path to run commands; | ||
|
||
'C:\Windows\syswow64\WindowsPowerShell\\v1.0\powershell.exe' | ||
|
||
If you prefer sewer to use as library, you can always override this while creating dns provider instance like below; | ||
|
||
overrided_power_shell_path = '...' | ||
WinDNS(zone=..., power_shell_path=overrided_power_shell_path) | ||
|
||
and if you prefer sewer-cli, the following parameters should work fine; | ||
|
||
python3 -m sewer ... --provider=windns --p_opt zone=example.com --p_opt power_shell_path=C:\Program Files... | ||
|
||
### One thing to be kept in mind while aliasing | ||
|
||
Since ```zone``` parameter is getting used by the driver to put TXT record to correct zone on DNS challenge. You should | ||
provide **the aliasing domain zone** which **CNAME points to**. Not that the domain that CNAME records live. | ||
|
||
As an example; domain names are used in [sewer's aliasing](https://github.com/komuw/sewer/blob/master/docs/Aliasing.md) | ||
documentation, you need to provide the following parameters; | ||
|
||
python3 -m sewer --domain name.example.com --provider=windns --p_opt zone=alias.org --p_opts alias=alias.org | ||
|
||
|
||
### If you installed DNSServer module and the driver is not able to initiate itself | ||
|
||
If the error statement is like below, | ||
|
||
DNSServer module seems it's not installed.. | ||
|
||
You should know that, if you are using 64 bit windows server, probably, there are two PowerShell variant on your | ||
machine. One supports 64 bit and other one support 32 bit, PowerShell (x86). The driver uses 64 bit one as default | ||
If you're comfortable to use 64 bit version, please, consider installing the DNSServer module to 64 bit one. | ||
On the other hand, if you want to keep forward with 32 bit version, I suggest you to override PowerShell path for sewer, | ||
which is mentioned in this document above. | ||
|
||
---- | ||
|
||
**If you're problem is not related with that, you may want to create an issue about it.** |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
from unittest import TestCase | ||
from unittest.mock import patch | ||
|
||
from sewer.lib import SewerError | ||
from sewer.providers.windns import WinDNS | ||
|
||
ACME_CHALLENGE = "_acme-challenge" | ||
|
||
|
||
class TestWinDNS(TestCase): | ||
def test_init_with_zone_provided(self): | ||
def _fn(): | ||
zone = "example.com" | ||
provider = WinDNS(zone) | ||
self.assertIsNotNone(provider) | ||
|
||
run_test_with_common_mocking(_fn) | ||
|
||
def test_init_with_zone_missing(self): | ||
def _fn(): | ||
with self.assertRaisesRegex( | ||
ValueError, "windns requires a string value for the zone argument" | ||
): | ||
WinDNS() | ||
|
||
run_test_with_common_mocking(_fn) | ||
|
||
def test_init_module_is_installed(self): | ||
run_test_with_common_mocking(lambda: WinDNS("example.com")) | ||
|
||
def test_init_module_is_not_installed(self): | ||
def _fn(): | ||
with self.assertRaisesRegex( | ||
SewerError, "It seems that, the DNSServer module is not installed" | ||
): | ||
WinDNS("example.com") | ||
|
||
run_test_with_common_mocking(_fn, False) | ||
|
||
def test_validate_domain_contains_zone(self): | ||
def _fn(): | ||
mock_challenge = _mock_challenge_1() | ||
mock_zone = "example.com" | ||
|
||
provider = WinDNS(mock_zone) | ||
provider._validate_domain_contains_zone(mock_challenge) | ||
|
||
run_test_with_common_mocking(_fn) | ||
|
||
def test_assert_domain_missing_zone(self): | ||
def _fn(): | ||
mock_challenge = _mock_challenge_1() | ||
mock_zone = "missing_zone" | ||
|
||
provider = WinDNS(mock_zone) | ||
|
||
with self.assertRaisesRegex(SewerError, "Domain must contains zone, domain"): | ||
provider._validate_domain_contains_zone(mock_challenge) | ||
|
||
run_test_with_common_mocking(_fn) | ||
|
||
def test_extract_sub_domain(self): | ||
def _test1(): | ||
mock_challenge = _mock_challenge_1() | ||
mock_zone = "example.com" | ||
|
||
provider = WinDNS(mock_zone) | ||
dns_name = provider._extract_sub_domain_from_challenge(mock_challenge) | ||
|
||
self.assertEqual(dns_name, ACME_CHALLENGE) | ||
|
||
def _test2(): | ||
mock_challenge = _mock_challenge_2() | ||
mock_zone = "example.com" | ||
|
||
provider = WinDNS(mock_zone) | ||
|
||
dns_name = provider._extract_sub_domain_from_challenge(mock_challenge) | ||
|
||
self.assertEqual(dns_name, "%s.test" % ACME_CHALLENGE) | ||
|
||
run_test_with_common_mocking(_test1) | ||
|
||
def test_alias_domain(self): | ||
def _fn(): | ||
mock_challenge = _mock_challenge_1() | ||
mock_alias = "alias.com" | ||
mock_alias_zone = mock_alias | ||
|
||
provider = WinDNS(mock_alias_zone, alias=mock_alias) | ||
|
||
dns_name = provider._extract_sub_domain_from_challenge(mock_challenge) | ||
|
||
mock_domain = mock_challenge["ident_value"] | ||
self.assertEqual(dns_name, mock_domain) | ||
|
||
run_test_with_common_mocking(_fn) | ||
|
||
|
||
def run_test_with_common_mocking(fn, is_module_installed=True): | ||
with patch( | ||
"windowsdnsserver.dns.dnsserver.DnsServerModule.is_dns_server_module_installed" | ||
) as mock_module_installed, patch("platform.system") as mock_platform: | ||
mock_module_installed.return_value = is_module_installed | ||
mock_platform.return_value = "Windows" | ||
|
||
fn() | ||
|
||
|
||
def _mock_challenge_1(): | ||
return {"ident_value": "example.com"} | ||
|
||
|
||
def _mock_challenge_2(): | ||
return {"ident_value": "test.example.com"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from sewer.auth import ChalListType, ErrataListType, DNSProviderBase | ||
from sewer.lib import dns_challenge, SewerError | ||
|
||
from windowsdnsserver.command_runner.powershell_runner import PowerShellRunner | ||
from windowsdnsserver.dns.dnsserver import DnsServerModule | ||
|
||
|
||
class WinDNS(DNSProviderBase): | ||
def __init__(self, zone=None, power_shell_path=None, **kwargs): | ||
super().__init__(**kwargs) | ||
|
||
if not isinstance(zone, str) or not zone: | ||
raise ValueError("windns requires a string value for the zone argument") | ||
|
||
self.zone = zone | ||
|
||
runner = None | ||
if power_shell_path: | ||
runner = PowerShellRunner(power_shell_path) | ||
|
||
self.dns = DnsServerModule(runner) | ||
|
||
if not self.dns.is_dns_server_module_installed(): | ||
raise SewerError( | ||
"It seems that, the DNSServer module is not installed. Please check the windns documentation.." | ||
) | ||
|
||
def setup(self, challenges: ChalListType) -> ErrataListType: | ||
for challenge in challenges: | ||
if not self.alias: | ||
self._validate_domain_contains_zone(challenge) | ||
|
||
name, txt_value = self._get_dns_name_and_text_for_challenge(challenge) | ||
self.dns.add_txt_record(self.zone, name, txt_value) | ||
|
||
return [] | ||
|
||
def unpropagated(self, challenges: ChalListType) -> ErrataListType: | ||
return [] | ||
|
||
def clear(self, challenges: ChalListType) -> ErrataListType: | ||
for challenge in challenges: | ||
name, txt_value = self._get_dns_name_and_text_for_challenge(challenge) | ||
self.dns.remove_txt_record(self.zone, name, txt_value) | ||
|
||
return [] | ||
|
||
# --- Challenge --- | ||
|
||
def _get_dns_name_and_text_for_challenge(self, challenge): | ||
name = self._extract_sub_domain_from_challenge(challenge) | ||
txt_value = dns_challenge(challenge["key_auth"]) | ||
|
||
return name, txt_value | ||
|
||
def _validate_domain_contains_zone(self, challenge): | ||
domain = self.target_domain(challenge) | ||
if self.zone not in domain: | ||
raise SewerError( | ||
"Domain must contains zone, domain: [%s], zone: [%s]" % (domain, self.zone) | ||
) | ||
|
||
def _extract_sub_domain_from_challenge(self, challenge): | ||
""" | ||
zone: example.com, domain: example.com ---> sub domain is "" | ||
zone: example.com, domain: test.asd.example.com ---> sub domain is "test.asd" | ||
zone: asd.example.com, domain: test.asd.example.com ---> sub domain is "test" | ||
""" | ||
domain = self.target_domain(challenge) | ||
|
||
sub_domain_index = domain.rfind(".%s" % self.zone) | ||
return domain[0:sub_domain_index] |