Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
acme-dns-tiny
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
1
Issues
1
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Adrien Dorsaz
acme-dns-tiny
Commits
436d055b
Commit
436d055b
authored
Jun 05, 2020
by
Adrien Dorsaz
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
apply pep8 hints and use brackets to split long lines
parent
8d346a56
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
80 additions
and
54 deletions
+80
-54
acme_dns_tiny.py
acme_dns_tiny.py
+23
-18
tests/config_factory.py
tests/config_factory.py
+6
-2
tests/staging_test_acme_account_deactivate.py
tests/staging_test_acme_account_deactivate.py
+4
-2
tests/staging_test_acme_account_rollover.py
tests/staging_test_acme_account_rollover.py
+6
-5
tests/staging_test_acme_dns_tiny.py
tests/staging_test_acme_dns_tiny.py
+21
-18
tests/unit_test_acme_dns_tiny.py
tests/unit_test_acme_dns_tiny.py
+2
-1
tools/acme_account_deactivate.py
tools/acme_account_deactivate.py
+9
-4
tools/acme_account_rollover.py
tools/acme_account_rollover.py
+9
-4
No files found.
acme_dns_tiny.py
View file @
436d055b
#!/usr/bin/env python3
#!/usr/bin/env python3
#pylint: disable=multiple-imports
#
pylint: disable=multiple-imports
"""ACME client to met DNS challenge and receive TLS certificate"""
"""ACME client to met DNS challenge and receive TLS certificate"""
import
argparse
,
base64
,
binascii
,
configparser
,
copy
,
hashlib
,
json
,
logging
import
argparse
,
base64
,
binascii
,
configparser
,
copy
,
hashlib
,
json
,
logging
import
re
,
sys
,
subprocess
,
time
import
re
,
sys
,
subprocess
,
time
...
@@ -8,10 +8,12 @@ import requests, dns.resolver, dns.tsigkeyring, dns.update
...
@@ -8,10 +8,12 @@ import requests, dns.resolver, dns.tsigkeyring, dns.update
LOGGER
=
logging
.
getLogger
(
'acme_dns_tiny'
)
LOGGER
=
logging
.
getLogger
(
'acme_dns_tiny'
)
LOGGER
.
addHandler
(
logging
.
StreamHandler
())
LOGGER
.
addHandler
(
logging
.
StreamHandler
())
def
_base64
(
text
):
def
_base64
(
text
):
"""
"
Encodes string as base64 as specified in the ACME RFC."""
"""Encodes string as base64 as specified in the ACME RFC."""
return
base64
.
urlsafe_b64encode
(
text
).
decode
(
"utf8"
).
rstrip
(
"="
)
return
base64
.
urlsafe_b64encode
(
text
).
decode
(
"utf8"
).
rstrip
(
"="
)
def
_openssl
(
command
,
options
,
communicate
=
None
):
def
_openssl
(
command
,
options
,
communicate
=
None
):
"""Run openssl command line and raise IOError on non-zero return."""
"""Run openssl command line and raise IOError on non-zero return."""
openssl
=
subprocess
.
Popen
([
"openssl"
,
command
]
+
options
,
openssl
=
subprocess
.
Popen
([
"openssl"
,
command
]
+
options
,
...
@@ -22,9 +24,10 @@ def _openssl(command, options, communicate=None):
...
@@ -22,9 +24,10 @@ def _openssl(command, options, communicate=None):
raise
IOError
(
"OpenSSL Error: {0}"
.
format
(
err
))
raise
IOError
(
"OpenSSL Error: {0}"
.
format
(
err
))
return
out
return
out
#pylint: disable=too-many-locals,too-many-branches,too-many-statements
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
def
get_crt
(
config
,
log
=
LOGGER
):
def
get_crt
(
config
,
log
=
LOGGER
):
"""Get ACME certificate by resolving DNS challenge"""
"""Get ACME certificate by resolving DNS challenge
.
"""
def
_update_dns
(
rrset
,
action
):
def
_update_dns
(
rrset
,
action
):
"""Updates DNS resource by adding or deleting resource."""
"""Updates DNS resource by adding or deleting resource."""
...
@@ -43,7 +46,7 @@ def get_crt(config, log=LOGGER):
...
@@ -43,7 +46,7 @@ def get_crt(config, log=LOGGER):
def
_send_signed_request
(
url
,
payload
,
extra_headers
=
None
):
def
_send_signed_request
(
url
,
payload
,
extra_headers
=
None
):
"""Sends signed requests to ACME server."""
"""Sends signed requests to ACME server."""
nonlocal
nonce
nonlocal
nonce
if
payload
==
""
:
# on POST-as-GET, final payload has to be just empty string
if
payload
==
""
:
# on POST-as-GET, final payload has to be just empty string
payload64
=
""
payload64
=
""
else
:
else
:
payload64
=
_base64
(
json
.
dumps
(
payload
).
encode
(
"utf8"
))
payload64
=
_base64
(
json
.
dumps
(
payload
).
encode
(
"utf8"
))
...
@@ -93,7 +96,7 @@ def get_crt(config, log=LOGGER):
...
@@ -93,7 +96,7 @@ def get_crt(config, log=LOGGER):
for
san
in
subject_alt_names
.
group
(
1
).
split
(
", "
):
for
san
in
subject_alt_names
.
group
(
1
).
split
(
", "
):
if
san
.
startswith
(
"DNS:"
):
if
san
.
startswith
(
"DNS:"
):
domains
.
add
(
san
[
4
:])
domains
.
add
(
san
[
4
:])
if
len
(
domains
)
==
0
:
#
pylint: disable=len-as-condition
if
len
(
domains
)
==
0
:
#
pylint: disable=len-as-condition
raise
ValueError
(
"Didn't find any domain to validate in the provided CSR."
)
raise
ValueError
(
"Didn't find any domain to validate in the provided CSR."
)
log
.
info
(
"Configure DNS client tools."
)
log
.
info
(
"Configure DNS client tools."
)
...
@@ -109,8 +112,8 @@ def get_crt(config, log=LOGGER):
...
@@ -109,8 +112,8 @@ def get_crt(config, log=LOGGER):
nameserver
=
nameserver
+
[
ipv6_rrset
.
to_text
()
for
ipv6_rrset
nameserver
=
nameserver
+
[
ipv6_rrset
.
to_text
()
for
ipv6_rrset
in
dns
.
resolver
.
query
(
config
[
"DNS"
][
"Host"
],
rdtype
=
"AAAA"
)]
in
dns
.
resolver
.
query
(
config
[
"DNS"
][
"Host"
],
rdtype
=
"AAAA"
)]
except
dns
.
exception
.
DNSException
:
except
dns
.
exception
.
DNSException
:
log
.
info
(
"A and/or AAAA DNS resources not found for configured dns host: we will use either
\
log
.
info
(
(
"A and/or AAAA DNS resources not found for configured dns host: we will use "
resource found if one exists or directly the DNS Host configuration."
)
"either resource found if one exists or directly the DNS Host configuration."
)
)
if
not
nameserver
:
if
not
nameserver
:
nameserver
=
[
config
[
"DNS"
][
"Host"
]]
nameserver
=
[
config
[
"DNS"
][
"Host"
]]
resolver
.
nameservers
=
nameserver
resolver
.
nameservers
=
nameserver
...
@@ -143,8 +146,8 @@ def get_crt(config, log=LOGGER):
...
@@ -143,8 +146,8 @@ def get_crt(config, log=LOGGER):
account_request
=
{}
account_request
=
{}
if
terms_service
:
if
terms_service
:
account_request
[
"termsOfServiceAgreed"
]
=
True
account_request
[
"termsOfServiceAgreed"
]
=
True
log
.
warning
(
"Terms of service exist and will be automatically agreed if possible,
\
log
.
warning
(
(
"Terms of service exist and will be automatically agreed if possible, "
you should read them: %s"
,
terms_service
)
"you should read them: %s"
)
,
terms_service
)
account_request
[
"contact"
]
=
config
[
"acmednstiny"
].
get
(
"Contacts"
,
""
).
split
(
';'
)
account_request
[
"contact"
]
=
config
[
"acmednstiny"
].
get
(
"Contacts"
,
""
).
split
(
';'
)
if
account_request
[
"contact"
]
==
[
""
]:
if
account_request
[
"contact"
]
==
[
""
]:
del
account_request
[
"contact"
]
del
account_request
[
"contact"
]
...
@@ -184,8 +187,8 @@ def get_crt(config, log=LOGGER):
...
@@ -184,8 +187,8 @@ def get_crt(config, log=LOGGER):
.
format
(
order
))
.
format
(
order
))
elif
(
http_response
.
status_code
==
403
elif
(
http_response
.
status_code
==
403
and
order
[
"type"
]
==
"urn:ietf:params:acme:error:userActionRequired"
):
and
order
[
"type"
]
==
"urn:ietf:params:acme:error:userActionRequired"
):
raise
ValueError
(
"Order creation failed ({0}). Read Terms of Service ({1}),
\
raise
ValueError
(
(
"Order creation failed ({0}). Read Terms of Service ({1}), then follow "
then follow your CA instructions: {2}"
"your CA instructions: {2}"
)
.
format
(
order
[
"detail"
],
http_response
.
headers
[
'Link'
],
order
[
"instance"
]))
.
format
(
order
[
"detail"
],
http_response
.
headers
[
'Link'
],
order
[
"instance"
]))
else
:
else
:
raise
ValueError
(
"Error getting new Order: {0} {1}"
raise
ValueError
(
"Error getting new Order: {0} {1}"
...
@@ -211,15 +214,15 @@ def get_crt(config, log=LOGGER):
...
@@ -211,15 +214,15 @@ def get_crt(config, log=LOGGER):
keyauthorization
=
"{0}.{1}"
.
format
(
token
,
jwk_thumbprint
)
keyauthorization
=
"{0}.{1}"
.
format
(
token
,
jwk_thumbprint
)
keydigest64
=
_base64
(
hashlib
.
sha256
(
keyauthorization
.
encode
(
"utf8"
)).
digest
())
keydigest64
=
_base64
(
hashlib
.
sha256
(
keyauthorization
.
encode
(
"utf8"
)).
digest
())
dnsrr_domain
=
"_acme-challenge.{0}."
.
format
(
domain
)
dnsrr_domain
=
"_acme-challenge.{0}."
.
format
(
domain
)
try
:
# a CNAME resource can be used for advanced TSIG configuration
try
:
# a CNAME resource can be used for advanced TSIG configuration
# Note: the CNAME target has to be of "non-CNAME" type (recursion isn't managed)
# Note: the CNAME target has to be of "non-CNAME" type (recursion isn't managed)
dnsrr_domain
=
[
response
.
to_text
()
for
response
dnsrr_domain
=
[
response
.
to_text
()
for
response
in
resolver
.
query
(
dnsrr_domain
,
rdtype
=
"CNAME"
)][
0
]
in
resolver
.
query
(
dnsrr_domain
,
rdtype
=
"CNAME"
)][
0
]
log
.
info
(
" - A CNAME resource has been found for this domain, will install TXT on %s"
,
log
.
info
(
" - A CNAME resource has been found for this domain, will install TXT on %s"
,
dnsrr_domain
)
dnsrr_domain
)
except
dns
.
exception
.
DNSException
as
dnsexception
:
except
dns
.
exception
.
DNSException
as
dnsexception
:
log
.
debug
(
" - Not any CNAME resource has been found for this domain (%s), will install
\
log
.
debug
(
(
" - Not any CNAME resource has been found for this domain (%s), will "
TXT directly on %s"
,
dnsrr_domain
,
type
(
dnsexception
).
__name__
)
"install TXT directly on %s"
)
,
dnsrr_domain
,
type
(
dnsexception
).
__name__
)
dnsrr_set
=
dns
.
rrset
.
from_text
(
dnsrr_domain
,
config
[
"DNS"
].
getint
(
"TTL"
),
dnsrr_set
=
dns
.
rrset
.
from_text
(
dnsrr_domain
,
config
[
"DNS"
].
getint
(
"TTL"
),
"IN"
,
"TXT"
,
'"{0}"'
.
format
(
keydigest64
))
"IN"
,
"TXT"
,
'"{0}"'
.
format
(
keydigest64
))
try
:
try
:
...
@@ -235,8 +238,8 @@ def get_crt(config, log=LOGGER):
...
@@ -235,8 +238,8 @@ def get_crt(config, log=LOGGER):
number_check_fail
=
1
number_check_fail
=
1
while
challenge_verified
is
False
:
while
challenge_verified
is
False
:
try
:
try
:
log
.
debug
(
'Self test (try: %s): Check resource with value "%s" exits on
\
log
.
debug
(
(
'Self test (try: %s): Check resource with value "%s" exits on '
nameservers: %s'
,
number_check_fail
,
keydigest64
,
resolver
.
nameservers
)
'nameservers: %s'
)
,
number_check_fail
,
keydigest64
,
resolver
.
nameservers
)
for
response
in
resolver
.
query
(
dnsrr_domain
,
rdtype
=
"TXT"
).
rrset
:
for
response
in
resolver
.
query
(
dnsrr_domain
,
rdtype
=
"TXT"
).
rrset
:
log
.
debug
(
" - Found value %s"
,
response
.
to_text
())
log
.
debug
(
" - Found value %s"
,
response
.
to_text
())
challenge_verified
=
(
challenge_verified
challenge_verified
=
(
challenge_verified
...
@@ -312,6 +315,7 @@ def get_crt(config, log=LOGGER):
...
@@ -312,6 +315,7 @@ def get_crt(config, log=LOGGER):
log
.
info
(
"Certificate signed and chain received: %s"
,
order
[
"certificate"
])
log
.
info
(
"Certificate signed and chain received: %s"
,
order
[
"certificate"
])
return
http_response
.
text
return
http_response
.
text
def
main
(
argv
):
def
main
(
argv
):
"""Parse arguments and get certificate."""
"""Parse arguments and get certificate."""
parser
=
argparse
.
ArgumentParser
(
parser
=
argparse
.
ArgumentParser
(
...
@@ -353,5 +357,6 @@ from the configuration file.")
...
@@ -353,5 +357,6 @@ from the configuration file.")
signed_crt
=
get_crt
(
config
,
LOGGER
)
signed_crt
=
get_crt
(
config
,
LOGGER
)
sys
.
stdout
.
write
(
signed_crt
)
sys
.
stdout
.
write
(
signed_crt
)
if
__name__
==
"__main__"
:
# pragma: no cover
if
__name__
==
"__main__"
:
# pragma: no cover
main
(
sys
.
argv
[
1
:])
main
(
sys
.
argv
[
1
:])
tests/config_factory.py
View file @
436d055b
...
@@ -18,6 +18,7 @@ TSIGKEYVALUE = os.getenv("GITLABCI_TSIGKEYVALUE")
...
@@ -18,6 +18,7 @@ TSIGKEYVALUE = os.getenv("GITLABCI_TSIGKEYVALUE")
TSIGALGORITHM
=
os
.
getenv
(
"GITLABCI_TSIGALGORITHM"
)
TSIGALGORITHM
=
os
.
getenv
(
"GITLABCI_TSIGALGORITHM"
)
CONTACT
=
os
.
getenv
(
"GITLABCI_CONTACT"
)
CONTACT
=
os
.
getenv
(
"GITLABCI_CONTACT"
)
def
generate_config
():
def
generate_config
():
"""Generate basic acme-dns-tiny configuration"""
"""Generate basic acme-dns-tiny configuration"""
# Account key
# Account key
...
@@ -50,6 +51,7 @@ def generate_config():
...
@@ -50,6 +51,7 @@ def generate_config():
return
account_key
.
name
,
domain_key
.
name
,
domain_csr
.
name
,
parser
return
account_key
.
name
,
domain_key
.
name
,
domain_csr
.
name
,
parser
def
generate_acme_dns_tiny_unit_test_config
():
def
generate_acme_dns_tiny_unit_test_config
():
"""Genereate acme_dns_tiny configurations used for unit tests"""
"""Genereate acme_dns_tiny configurations used for unit tests"""
# Configuration missing DNS section
# Configuration missing DNS section
...
@@ -63,7 +65,8 @@ def generate_acme_dns_tiny_unit_test_config():
...
@@ -63,7 +65,8 @@ def generate_acme_dns_tiny_unit_test_config():
return
{
"missing_dns"
:
missing_dns
.
name
}
return
{
"missing_dns"
:
missing_dns
.
name
}
def
generate_acme_dns_tiny_config
():
#pylint: disable=too-many-locals,too-many-statements
def
generate_acme_dns_tiny_config
():
# pylint: disable=too-many-locals,too-many-statements
"""Generate acme_dns_tiny configuration with account and domain keys"""
"""Generate acme_dns_tiny configuration with account and domain keys"""
# Simple configuration with good options
# Simple configuration with good options
account_key
,
domain_key
,
_
,
config
=
generate_config
()
account_key
,
domain_key
,
_
,
config
=
generate_config
()
...
@@ -132,7 +135,6 @@ def generate_acme_dns_tiny_config(): #pylint: disable=too-many-locals,too-many-s
...
@@ -132,7 +135,6 @@ def generate_acme_dns_tiny_config(): #pylint: disable=too-many-locals,too-many-s
with
open
(
good_san
.
name
,
'w'
)
as
configfile
:
with
open
(
good_san
.
name
,
'w'
)
as
configfile
:
config
.
write
(
configfile
)
config
.
write
(
configfile
)
# Configuration with CSR containing a wildcard domain inside subjetcAltName
# Configuration with CSR containing a wildcard domain inside subjetcAltName
account_key
,
domain_key
,
domain_csr
,
config
=
generate_config
()
account_key
,
domain_key
,
domain_csr
,
config
=
generate_config
()
...
@@ -198,6 +200,7 @@ def generate_acme_dns_tiny_config(): #pylint: disable=too-many-locals,too-many-s
...
@@ -198,6 +200,7 @@ def generate_acme_dns_tiny_config(): #pylint: disable=too-many-locals,too-many-s
"cname_csr"
:
cname_csr
,
"cname_csr"
:
cname_csr
,
}
}
def
generate_acme_account_rollover_config
():
def
generate_acme_account_rollover_config
():
"""Generate config for acme_account_rollover script"""
"""Generate config for acme_account_rollover script"""
# Old account key is directly created by the config generator
# Old account key is directly created by the config generator
...
@@ -219,6 +222,7 @@ def generate_acme_account_rollover_config():
...
@@ -219,6 +222,7 @@ def generate_acme_account_rollover_config():
"new_account_key"
:
new_account_key
.
name
"new_account_key"
:
new_account_key
.
name
}
}
def
generate_acme_account_deactivate_config
():
def
generate_acme_account_deactivate_config
():
"""Generate config for acme_account_deactivate script"""
"""Generate config for acme_account_deactivate script"""
# Account key is created by the by the config generator
# Account key is created by the by the config generator
...
...
tests/staging_test_acme_account_deactivate.py
View file @
436d055b
...
@@ -9,8 +9,9 @@ import tools.acme_account_deactivate
...
@@ -9,8 +9,9 @@ import tools.acme_account_deactivate
ACME_DIRECTORY
=
os
.
getenv
(
"GITLABCI_ACMEDIRECTORY_V2"
,
ACME_DIRECTORY
=
os
.
getenv
(
"GITLABCI_ACMEDIRECTORY_V2"
,
"https://acme-staging-v02.api.letsencrypt.org/directory"
)
"https://acme-staging-v02.api.letsencrypt.org/directory"
)
class
TestACMEAccountDeactivate
(
unittest
.
TestCase
):
class
TestACMEAccountDeactivate
(
unittest
.
TestCase
):
"
Tests for acme_account_deactivate
"
"
""Tests for acme_account_deactivate.""
"
@
classmethod
@
classmethod
def
setUpClass
(
cls
):
def
setUpClass
(
cls
):
...
@@ -25,7 +26,7 @@ class TestACMEAccountDeactivate(unittest.TestCase):
...
@@ -25,7 +26,7 @@ class TestACMEAccountDeactivate(unittest.TestCase):
super
(
TestACMEAccountDeactivate
,
cls
).
setUpClass
()
super
(
TestACMEAccountDeactivate
,
cls
).
setUpClass
()
# To clean ACME staging server and close correctly temporary files
# To clean ACME staging server and close correctly temporary files
#pylint: disable=bare-except
#
pylint: disable=bare-except
@
classmethod
@
classmethod
def
tearDownClass
(
cls
):
def
tearDownClass
(
cls
):
# Remove temporary files
# Remove temporary files
...
@@ -53,5 +54,6 @@ class TestACMEAccountDeactivate(unittest.TestCase):
...
@@ -53,5 +54,6 @@ class TestACMEAccountDeactivate(unittest.TestCase):
self
.
assertIn
(
"INFO:acme_account_deactivate:The account has been deactivated."
,
self
.
assertIn
(
"INFO:acme_account_deactivate:The account has been deactivated."
,
accountdeactivatelog
.
output
)
accountdeactivatelog
.
output
)
if
__name__
==
"__main__"
:
# pragma: no cover
if
__name__
==
"__main__"
:
# pragma: no cover
unittest
.
main
()
unittest
.
main
()
tests/staging_test_acme_account_rollover.py
View file @
436d055b
...
@@ -10,8 +10,9 @@ import tools.acme_account_rollover
...
@@ -10,8 +10,9 @@ import tools.acme_account_rollover
ACME_DIRECTORY
=
os
.
getenv
(
"GITLABCI_ACMEDIRECTORY_V2"
,
ACME_DIRECTORY
=
os
.
getenv
(
"GITLABCI_ACMEDIRECTORY_V2"
,
"https://acme-staging-v02.api.letsencrypt.org/directory"
)
"https://acme-staging-v02.api.letsencrypt.org/directory"
)
class
TestACMEAccountRollover
(
unittest
.
TestCase
):
class
TestACMEAccountRollover
(
unittest
.
TestCase
):
"
Tests for acme_account_rollover
"
"
""Tests for acme_account_rollover.""
"
@
classmethod
@
classmethod
def
setUpClass
(
cls
):
def
setUpClass
(
cls
):
...
@@ -20,7 +21,7 @@ class TestACMEAccountRollover(unittest.TestCase):
...
@@ -20,7 +21,7 @@ class TestACMEAccountRollover(unittest.TestCase):
super
(
TestACMEAccountRollover
,
cls
).
setUpClass
()
super
(
TestACMEAccountRollover
,
cls
).
setUpClass
()
# To clean ACME staging server and close correctly temporary files
# To clean ACME staging server and close correctly temporary files
#pylint: disable=bare-except
#
pylint: disable=bare-except
@
classmethod
@
classmethod
def
tearDownClass
(
cls
):
def
tearDownClass
(
cls
):
# Remove temporary files
# Remove temporary files
...
@@ -51,13 +52,13 @@ class TestACMEAccountRollover(unittest.TestCase):
...
@@ -51,13 +52,13 @@ class TestACMEAccountRollover(unittest.TestCase):
super
(
TestACMEAccountRollover
,
cls
).
tearDownClass
()
super
(
TestACMEAccountRollover
,
cls
).
tearDownClass
()
def
test_success_account_rollover
(
self
):
def
test_success_account_rollover
(
self
):
""" Test success account key rollover
"""
""" Test success account key rollover
.
"""
with
self
.
assertLogs
(
level
=
'INFO'
)
as
accountrolloverlog
:
with
self
.
assertLogs
(
level
=
'INFO'
)
as
accountrolloverlog
:
tools
.
acme_account_rollover
.
main
([
"--current"
,
self
.
configs
[
'old_account_key'
],
tools
.
acme_account_rollover
.
main
([
"--current"
,
self
.
configs
[
'old_account_key'
],
"--new"
,
self
.
configs
[
'new_account_key'
],
"--new"
,
self
.
configs
[
'new_account_key'
],
"--acme-directory"
,
ACME_DIRECTORY
])
"--acme-directory"
,
ACME_DIRECTORY
])
self
.
assertIn
(
"INFO:acme_account_rollover:Keys rolled over."
,
self
.
assertIn
(
"INFO:acme_account_rollover:Keys rolled over."
,
accountrolloverlog
.
output
)
accountrolloverlog
.
output
)
if
__name__
==
"__main__"
:
# pragma: no cover
if
__name__
==
"__main__"
:
# pragma: no cover
unittest
.
main
()
unittest
.
main
()
tests/staging_test_acme_dns_tiny.py
View file @
436d055b
...
@@ -13,8 +13,9 @@ from tools.acme_account_deactivate import account_deactivate
...
@@ -13,8 +13,9 @@ from tools.acme_account_deactivate import account_deactivate
ACME_DIRECTORY
=
os
.
getenv
(
"GITLABCI_ACMEDIRECTORY_V2"
,
ACME_DIRECTORY
=
os
.
getenv
(
"GITLABCI_ACMEDIRECTORY_V2"
,
"https://acme-staging-v02.api.letsencrypt.org/directory"
)
"https://acme-staging-v02.api.letsencrypt.org/directory"
)
def
_openssl
(
command
,
options
,
communicate
=
None
):
def
_openssl
(
command
,
options
,
communicate
=
None
):
"""Helper function to run openssl command"""
"""Helper function to run openssl command
.
"""
openssl
=
subprocess
.
Popen
([
"openssl"
,
command
]
+
options
,
openssl
=
subprocess
.
Popen
([
"openssl"
,
command
]
+
options
,
stdin
=
subprocess
.
PIPE
,
stdout
=
subprocess
.
PIPE
,
stdin
=
subprocess
.
PIPE
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
)
stderr
=
subprocess
.
PIPE
)
...
@@ -23,8 +24,9 @@ def _openssl(command, options, communicate=None):
...
@@ -23,8 +24,9 @@ def _openssl(command, options, communicate=None):
raise
IOError
(
"OpenSSL Error: {0}"
.
format
(
err
))
raise
IOError
(
"OpenSSL Error: {0}"
.
format
(
err
))
return
out
.
decode
(
"utf8"
)
return
out
.
decode
(
"utf8"
)
class
TestACMEDNSTiny
(
unittest
.
TestCase
):
class
TestACMEDNSTiny
(
unittest
.
TestCase
):
"
Tests for acme_dns_tiny.get_crt()
"
"
""Tests for acme_dns_tiny.get_crt().""
"
@
classmethod
@
classmethod
def
setUpClass
(
cls
):
def
setUpClass
(
cls
):
...
@@ -36,7 +38,7 @@ class TestACMEDNSTiny(unittest.TestCase):
...
@@ -36,7 +38,7 @@ class TestACMEDNSTiny(unittest.TestCase):
super
(
TestACMEDNSTiny
,
cls
).
setUpClass
()
super
(
TestACMEDNSTiny
,
cls
).
setUpClass
()
# To clean ACME staging server and close correctly temporary files
# To clean ACME staging server and close correctly temporary files
#pylint: disable=bare-except
#
pylint: disable=bare-except
@
classmethod
@
classmethod
def
tearDownClass
(
cls
):
def
tearDownClass
(
cls
):
# close temp files correctly
# close temp files correctly
...
@@ -77,7 +79,7 @@ class TestACMEDNSTiny(unittest.TestCase):
...
@@ -77,7 +79,7 @@ class TestACMEDNSTiny(unittest.TestCase):
self
.
assertIn
(
"Issuer"
,
readablecertchain
)
self
.
assertIn
(
"Issuer"
,
readablecertchain
)
def
test_success_cn
(
self
):
def
test_success_cn
(
self
):
"""
Successfully issue a certificate via common name
"""
"""
Successfully issue a certificate via common name.
"""
old_stdout
=
sys
.
stdout
old_stdout
=
sys
.
stdout
sys
.
stdout
=
StringIO
()
sys
.
stdout
=
StringIO
()
...
@@ -90,7 +92,7 @@ class TestACMEDNSTiny(unittest.TestCase):
...
@@ -90,7 +92,7 @@ class TestACMEDNSTiny(unittest.TestCase):
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_success_cn_without_contacts
(
self
):
def
test_success_cn_without_contacts
(
self
):
"""
Successfully issue a certificate via CN, but without Contacts field
"""
"""
Successfully issue a certificate via CN, but without Contacts field.
"""
old_stdout
=
sys
.
stdout
old_stdout
=
sys
.
stdout
sys
.
stdout
=
StringIO
()
sys
.
stdout
=
StringIO
()
...
@@ -103,7 +105,7 @@ class TestACMEDNSTiny(unittest.TestCase):
...
@@ -103,7 +105,7 @@ class TestACMEDNSTiny(unittest.TestCase):
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_success_cn_with_csr_option
(
self
):
def
test_success_cn_with_csr_option
(
self
):
"""
Successfully issue a certificate using CSR option outside from the config file
"""
"""
Successfully issue a certificate using CSR option outside from the config file.
"""
old_stdout
=
sys
.
stdout
old_stdout
=
sys
.
stdout
sys
.
stdout
=
StringIO
()
sys
.
stdout
=
StringIO
()
...
@@ -117,7 +119,7 @@ class TestACMEDNSTiny(unittest.TestCase):
...
@@ -117,7 +119,7 @@ class TestACMEDNSTiny(unittest.TestCase):
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_success_wild_cn
(
self
):
def
test_success_wild_cn
(
self
):
"""
Successfully issue a certificate via a wildcard common name
"""
"""
Successfully issue a certificate via a wildcard common name.
"""
old_stdout
=
sys
.
stdout
old_stdout
=
sys
.
stdout
sys
.
stdout
=
StringIO
()
sys
.
stdout
=
StringIO
()
...
@@ -130,16 +132,16 @@ class TestACMEDNSTiny(unittest.TestCase):
...
@@ -130,16 +132,16 @@ class TestACMEDNSTiny(unittest.TestCase):
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_success_dnshost_ip
(
self
):
def
test_success_dnshost_ip
(
self
):
"""
When DNS Host is an IP, DNS resolution have to fail without error
"""
"""
When DNS Host is an IP, DNS resolution have to fail without error.
"""
old_stdout
=
sys
.
stdout
old_stdout
=
sys
.
stdout
sys
.
stdout
=
StringIO
()
sys
.
stdout
=
StringIO
()
with
self
.
assertLogs
(
level
=
'INFO'
)
as
adnslog
:
with
self
.
assertLogs
(
level
=
'INFO'
)
as
adnslog
:
acme_dns_tiny
.
main
([
self
.
configs
[
'dns_host_ip'
],
acme_dns_tiny
.
main
([
self
.
configs
[
'dns_host_ip'
],
"--verbose"
])
"--verbose"
])
self
.
assertIn
(
"INFO:acme_dns_tiny:A and/or AAAA DNS resources not found for configured dns
\
self
.
assertIn
(
(
"INFO:acme_dns_tiny:A and/or AAAA DNS resources not found for configured "
host: we will use either resource found if one exists or directly the DNS Host configuration."
,
"dns host: we will use either resource found if one exists or directly the "
adnslog
.
output
)
"DNS Host configuration."
),
adnslog
.
output
)
certchain
=
sys
.
stdout
.
getvalue
()
certchain
=
sys
.
stdout
.
getvalue
()
sys
.
stdout
.
close
()
sys
.
stdout
.
close
()
...
@@ -148,7 +150,7 @@ host: we will use either resource found if one exists or directly the DNS Host c
...
@@ -148,7 +150,7 @@ host: we will use either resource found if one exists or directly the DNS Host c
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_success_san
(
self
):
def
test_success_san
(
self
):
"""
Successfully issue a certificate via subject alt name
"""
"""
Successfully issue a certificate via subject alt name.
"""
old_stdout
=
sys
.
stdout
old_stdout
=
sys
.
stdout
sys
.
stdout
=
StringIO
()
sys
.
stdout
=
StringIO
()
...
@@ -161,7 +163,7 @@ host: we will use either resource found if one exists or directly the DNS Host c
...
@@ -161,7 +163,7 @@ host: we will use either resource found if one exists or directly the DNS Host c
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_success_wildsan
(
self
):
def
test_success_wildsan
(
self
):
"""
Successfully issue a certificate via wildcard in subject alt name
"""
"""
Successfully issue a certificate via wildcard in subject alt name.
"""
old_stdout
=
sys
.
stdout
old_stdout
=
sys
.
stdout
sys
.
stdout
=
StringIO
()
sys
.
stdout
=
StringIO
()
...
@@ -174,7 +176,7 @@ host: we will use either resource found if one exists or directly the DNS Host c
...
@@ -174,7 +176,7 @@ host: we will use either resource found if one exists or directly the DNS Host c
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_success_cli
(
self
):
def
test_success_cli
(
self
):
"""
Successfully issue a certificate via command line interface
"""
"""
Successfully issue a certificate via command line interface.
"""
certout
,
_
=
subprocess
.
Popen
([
certout
,
_
=
subprocess
.
Popen
([
"python3"
,
"acme_dns_tiny.py"
,
self
.
configs
[
'good_cname'
],
"--verbose"
"python3"
,
"acme_dns_tiny.py"
,
self
.
configs
[
'good_cname'
],
"--verbose"
],
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
).
communicate
()
],
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
).
communicate
()
...
@@ -184,7 +186,7 @@ host: we will use either resource found if one exists or directly the DNS Host c
...
@@ -184,7 +186,7 @@ host: we will use either resource found if one exists or directly the DNS Host c
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_success_cli_with_csr_option
(
self
):
def
test_success_cli_with_csr_option
(
self
):
"""
Successfully issue a certificate via command line interface using CSR option
"""
"""
Successfully issue a certificate via command line interface using CSR option.
"""
certout
,
_
=
subprocess
.
Popen
([
certout
,
_
=
subprocess
.
Popen
([
"python3"
,
"acme_dns_tiny.py"
,
"--csr"
,
self
.
configs
[
'cname_csr'
],
"python3"
,
"acme_dns_tiny.py"
,
"--csr"
,
self
.
configs
[
'cname_csr'
],
self
.
configs
[
'good_cname_without_csr'
],
"--verbose"
self
.
configs
[
'good_cname_without_csr'
],
"--verbose"
...
@@ -195,22 +197,23 @@ host: we will use either resource found if one exists or directly the DNS Host c
...
@@ -195,22 +197,23 @@ host: we will use either resource found if one exists or directly the DNS Host c
self
.
_assert_certificate_chain
(
certchain
)
self
.
_assert_certificate_chain
(
certchain
)
def
test_weak_key
(
self
):
def
test_weak_key
(
self
):
"""
Let's Encrypt rejects weak keys
"""
"""
Let's Encrypt rejects weak keys.
"""
self
.
assertRaisesRegex
(
ValueError
,
self
.
assertRaisesRegex
(
ValueError
,
"key too small"
,
"key too small"
,
acme_dns_tiny
.
main
,
[
self
.
configs
[
'weak_key'
],
"--verbose"
])
acme_dns_tiny
.
main
,
[
self
.
configs
[
'weak_key'
],
"--verbose"
])
def
test_account_key_domain
(
self
):
def
test_account_key_domain
(
self
):
"""
Can't use the account key for the CSR
"""
"""
Can't use the account key for the CSR.
"""
self
.
assertRaisesRegex
(
ValueError
,
self
.
assertRaisesRegex
(
ValueError
,
"certificate public key must be different than account key"
,
"certificate public key must be different than account key"
,
acme_dns_tiny
.
main
,
[
self
.
configs
[
'account_as_domain'
],
"--verbose"
])
acme_dns_tiny
.
main
,
[
self
.
configs
[
'account_as_domain'
],
"--verbose"
])
def
test_failure_dns_update_tsigkeyname
(
self
):
def
test_failure_dns_update_tsigkeyname
(
self
):
"""
Fail to update DNS records by invalid TSIG Key name
"""
"""
Fail to update DNS records by invalid TSIG Key name.
"""
self
.
assertRaisesRegex
(
ValueError
,
self
.
assertRaisesRegex
(
ValueError
,
"Error updating DNS"
,
"Error updating DNS"
,
acme_dns_tiny
.
main
,
[
self
.
configs
[
'invalid_tsig_name'
],
"--verbose"
])
acme_dns_tiny
.
main
,
[
self
.
configs
[
'invalid_tsig_name'
],
"--verbose"
])
if
__name__
==
"__main__"
:
# pragma: no cover
if
__name__
==
"__main__"
:
# pragma: no cover
unittest
.
main
()
unittest
.
main
()
tests/unit_test_acme_dns_tiny.py
View file @
436d055b
...
@@ -7,6 +7,7 @@ import dns.version
...
@@ -7,6 +7,7 @@ import dns.version
import
acme_dns_tiny
import
acme_dns_tiny
from
tests.config_factory
import
generate_acme_dns_tiny_unit_test_config
from
tests.config_factory
import
generate_acme_dns_tiny_unit_test_config
class
TestACMEDNSTiny
(
unittest
.
TestCase
):
class
TestACMEDNSTiny
(
unittest
.
TestCase
):
"Tests for acme_dns_tiny.get_crt()"
"Tests for acme_dns_tiny.get_crt()"
...
@@ -31,11 +32,11 @@ class TestACMEDNSTiny(unittest.TestCase):
...
@@ -31,11 +32,11 @@ class TestACMEDNSTiny(unittest.TestCase):
os
.
remove
(
cls
.
configs
[
conffile
])
os
.
remove
(
cls
.
configs
[
conffile
])
super
(
TestACMEDNSTiny
,
cls
).
tearDownClass
()
super
(
TestACMEDNSTiny
,
cls
).
tearDownClass
()
def
test_failure_notcompleted_configuration
(
self
):
def
test_failure_notcompleted_configuration
(
self
):
""" Configuration file have to be completed """
""" Configuration file have to be completed """
self
.
assertRaisesRegex
(
ValueError
,
r
"Some required settings are missing."
,
self
.
assertRaisesRegex
(
ValueError
,
r
"Some required settings are missing."
,
acme_dns_tiny
.
main
,
[
self
.
configs
[
'missing_dns'
],
"--verbose"
])
acme_dns_tiny
.
main
,
[
self
.
configs
[
'missing_dns'
],
"--verbose"
])
if
__name__
==
"__main__"
:
# pragma: no cover