Comparing existing records with required
This commit is contained in:
parent
4fe359a3c1
commit
47d8f6212c
|
@ -0,0 +1,2 @@
|
|||
- https://github.com/shuque/tlsa_rdata for being a huge help in generating the TLSA record variants
|
||||
- https://letsdns.org/ another TLSA record generator, not intertwined with certbot
|
|
@ -15,8 +15,8 @@ from certbot._internal.storage import renewal_conf_files
|
|||
from certbot.crypto_util import verify_renewable_cert
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..utils.cert import DaneCert
|
||||
from ..utils.config import Configuration
|
||||
from ..utils.config_types.cert import DaneRenewableCert
|
||||
|
||||
_LOGGER = getLogger(name=__name__)
|
||||
|
||||
|
@ -24,8 +24,8 @@ _LOGGER = getLogger(name=__name__)
|
|||
class _ParsedCertsResult(NamedTuple):
|
||||
'Result of a `_get_parsed_certs()` call.'
|
||||
# A `MappingProxyType` is basically a read-only `dict`
|
||||
# config path -> DaneRenewableCert
|
||||
parsed_certs: MappingProxyType[str, DaneRenewableCert]
|
||||
# config path -> DaneCert
|
||||
parsed_certs: MappingProxyType[str, DaneCert]
|
||||
# lineage name -> config path
|
||||
lineages: MappingProxyType[str, str]
|
||||
parse_failures: tuple[str, ...]
|
||||
|
@ -62,12 +62,12 @@ class HandlerBase(object, metaclass=ABCMeta):
|
|||
|
||||
See `certbot._internal.cert_manager.certificates()`.
|
||||
"""
|
||||
parsed_certs = dict[str, DaneRenewableCert]()
|
||||
parsed_certs = dict[str, DaneCert]()
|
||||
parse_failures = tuple[str, ...]()
|
||||
lineages = dict[str, str]()
|
||||
for renewal_file in renewal_conf_files(config=self._certbot_config):
|
||||
try:
|
||||
renewal_candidate = DaneRenewableCert.from_data(
|
||||
renewal_candidate = DaneCert.from_configdata(
|
||||
config_filename=renewal_file,
|
||||
cli_config=self._certbot_config)
|
||||
verify_renewable_cert(renewable_cert=renewal_candidate)
|
||||
|
|
|
@ -23,7 +23,7 @@ from certbot.interfaces import Authenticator, Installer
|
|||
from certbot.util import Key
|
||||
from ktools.pathlib import Path
|
||||
|
||||
from ..utils.config_types.cert import DaneRenewableCert
|
||||
from ..utils.cert import DaneCert
|
||||
from ..utils.tlsa.updater import TlsaUpdater
|
||||
from .base import HandlerBase
|
||||
|
||||
|
@ -39,14 +39,11 @@ _LOGGER = getLogger(name=__name__)
|
|||
|
||||
|
||||
class _CertbotLogicEmulator(object):
|
||||
"""
|
||||
Emulating certbot renewal operations, for **one**
|
||||
`DaneRenewableCert`.
|
||||
"""
|
||||
'Emulating certbot renewal operations, for **one** `DaneCert`.'
|
||||
|
||||
def __init__(
|
||||
self, certbot_config: NamespaceConfig, plugins: PluginsRegistry,
|
||||
cert: DaneRenewableCert):
|
||||
cert: DaneCert):
|
||||
self._certbot_config = certbot_config
|
||||
self.cert = cert
|
||||
self._plugins = plugins
|
||||
|
@ -186,7 +183,7 @@ class RenewHandler(HandlerBase):
|
|||
return args
|
||||
|
||||
def _put_in_place_when_ttl_passed(
|
||||
self, certconfig_path: str, cert: DaneRenewableCert) -> list[str]:
|
||||
self, certconfig_path: str, cert: DaneCert) -> list[str]:
|
||||
"""
|
||||
If the cert `mtime` has passed the TLSA record's TTL, put it in
|
||||
place.
|
||||
|
@ -208,8 +205,7 @@ class RenewHandler(HandlerBase):
|
|||
return certbot_emu.run_installer_logic()
|
||||
|
||||
def _renew_one_adopted(
|
||||
self, certconfig_path: str, cert: DaneRenewableCert
|
||||
) -> list[str]:
|
||||
self, certconfig_path: str, cert: DaneCert) -> list[str]:
|
||||
"""
|
||||
Handle renewal/update of an adopted certificate.
|
||||
:returns: The list of renewed domains in case a previously
|
||||
|
@ -235,14 +231,14 @@ class RenewHandler(HandlerBase):
|
|||
return []
|
||||
|
||||
def _renew_one_nonadopted(
|
||||
self, cert: DaneRenewableCert) -> tuple[list[str], list[str]]:
|
||||
self, cert: DaneCert) -> tuple[list[str], list[str]]:
|
||||
'Renew a non-adopted certificate by running certbot\'s logic.'
|
||||
config = deepcopy(self._certbot_config)
|
||||
config.certname = cert.lineagename
|
||||
return handle_renewal_request(config=config)
|
||||
|
||||
def _start_renewing(
|
||||
self, to_renew: dict[str, DaneRenewableCert],
|
||||
self, to_renew: dict[str, DaneCert],
|
||||
already_adopted: frozenset[str]) -> int:
|
||||
'Start renewing the chosen certificates.'
|
||||
renewed_domains = list[str]()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
from functools import cached_property, lru_cache
|
||||
from hashlib import sha256, sha512
|
||||
from logging import getLogger
|
||||
from typing import Literal, Mapping
|
||||
|
||||
|
@ -11,14 +12,39 @@ from certbot.compat.filesystem import (
|
|||
compute_private_key_mode, copy_ownership_and_apply_mode)
|
||||
from certbot.configuration import NamespaceConfig
|
||||
from certbot.util import Key
|
||||
from cryptography.hazmat.primitives._serialization import (
|
||||
Encoding, PublicFormat)
|
||||
from cryptography.x509.base import Certificate, load_pem_x509_certificate
|
||||
from ktools.pathlib import Path
|
||||
|
||||
from .config_types.common import TlsaMatchingType, TlsaSelector, TlsaUsage
|
||||
from .tlsa.record_type import TlsaRecordType
|
||||
|
||||
KindType = Literal['cert', 'privkey', 'chain', 'fullchain']
|
||||
_TargetDictType = dict[KindType, Path]
|
||||
_LOGGER = getLogger(name=__name__)
|
||||
|
||||
|
||||
class DaneRenewableCert(RenewableCert):
|
||||
def get_tlsa_bytes(
|
||||
cert: Certificate, selector: TlsaSelector,
|
||||
matching_type: TlsaMatchingType
|
||||
) -> bytes:
|
||||
'Return a generated `bytes` data of the requested variant.'
|
||||
if selector == TlsaSelector.ENTIRE:
|
||||
data = cert.public_bytes(encoding=Encoding.DER)
|
||||
elif selector == TlsaSelector.PUBLICKEY:
|
||||
data = cert.public_key().public_bytes(
|
||||
encoding=Encoding.DER,
|
||||
format=PublicFormat.SubjectPublicKeyInfo)
|
||||
if matching_type == TlsaMatchingType.ENTIRE:
|
||||
return data
|
||||
elif matching_type == TlsaMatchingType.SHA256:
|
||||
return sha256(string=data).digest()
|
||||
elif matching_type == TlsaMatchingType.SHA512:
|
||||
return sha512(string=data).digest()
|
||||
|
||||
|
||||
class DaneCert(RenewableCert):
|
||||
"""
|
||||
Wrapping and extending a `RenewableCert` to have added methods &
|
||||
functionality.
|
||||
|
@ -26,7 +52,8 @@ class DaneRenewableCert(RenewableCert):
|
|||
_wrapped_renewablecert: RenewableCert
|
||||
_UPCOMING_DIRNAME = 'upcoming'
|
||||
__CACHED_PROPERTIES = (
|
||||
'latest_upcoming_mtime', 'common_names', 'all_upcomings_in_place')
|
||||
'latest_upcoming_mtime', 'common_names', 'all_upcomings_in_place',
|
||||
'_DaneCert__upcoming_pubcert', '_DaneCert__pubcert')
|
||||
|
||||
def __init__(self):
|
||||
'Placeholder to override parent\'s `__init__`'
|
||||
|
@ -34,25 +61,27 @@ class DaneRenewableCert(RenewableCert):
|
|||
def __reset_cache(self):
|
||||
'Resed cached properties.'
|
||||
for name in self.__CACHED_PROPERTIES:
|
||||
if hasattr(self, name):
|
||||
if name in self.__dict__:
|
||||
delattr(self, name)
|
||||
self.get_tlsa_recordtypes.cache_clear()
|
||||
|
||||
@staticmethod
|
||||
def from_renewablecert(cert: RenewableCert) -> DaneRenewableCert:
|
||||
result = DaneRenewableCert()
|
||||
def from_renewablecert(cert: RenewableCert) -> DaneCert:
|
||||
'Return a `DaneCert` from a `RenewableCert`.'
|
||||
result = DaneCert()
|
||||
result.__dict__ = cert.__dict__
|
||||
result._wrapped_renewablecert = cert
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def from_data(
|
||||
config_filename: str, cli_config: NamespaceConfig,
|
||||
update_symlinks: bool = False
|
||||
) -> DaneRenewableCert:
|
||||
def from_configdata(
|
||||
config_filename: str, cli_config: NamespaceConfig,
|
||||
update_symlinks: bool = False) -> DaneCert:
|
||||
'Return a `DaneCert` from configuration data.'
|
||||
cert = RenewableCert(
|
||||
config_filename=config_filename, cli_config=cli_config,
|
||||
update_symlinks=update_symlinks)
|
||||
return DaneRenewableCert.from_renewablecert(cert=cert)
|
||||
return DaneCert.from_renewablecert(cert=cert)
|
||||
|
||||
@cached_property
|
||||
def upcoming_dir(self) -> Path:
|
||||
|
@ -65,25 +94,26 @@ class DaneRenewableCert(RenewableCert):
|
|||
self._UPCOMING_DIRNAME).joinpath(original_path.name)
|
||||
|
||||
@cached_property
|
||||
def all_upcomings_in_place(self) -> list[Path]:
|
||||
def all_upcomings_in_place(self) -> _TargetDictType:
|
||||
'Return all of the upcoming certificate `Path`s.'
|
||||
self.upcoming_dir.mkdir(exist_ok=True)
|
||||
next_version = self.next_free_version()
|
||||
all_upcoming = [
|
||||
self.get_upcoming_path(kind=kind, version=next_version)
|
||||
for kind in ALL_FOUR]
|
||||
if all(Path.is_file(x) for x in all_upcoming):
|
||||
all_upcoming: _TargetDictType = {
|
||||
kind: self.get_upcoming_path(kind=kind, version=next_version)
|
||||
for kind in ALL_FOUR}
|
||||
if all(Path.is_file(x) for x in all_upcoming.values()):
|
||||
return all_upcoming
|
||||
for upcoming_path in all_upcoming:
|
||||
for upcoming_path in all_upcoming.values():
|
||||
upcoming_path.unlink(missing_ok=True)
|
||||
return []
|
||||
return {}
|
||||
|
||||
@cached_property
|
||||
def latest_upcoming_mtime(self) -> float:
|
||||
'Return the latest mtime of the upcoming certificate.'
|
||||
return max(
|
||||
(Path.stat(x).st_mtime for x in self.all_upcomings_in_place),
|
||||
default=0)
|
||||
return max((
|
||||
Path.stat(x).st_mtime
|
||||
for x in self.all_upcomings_in_place.values()
|
||||
), default=0)
|
||||
|
||||
@cached_property
|
||||
def common_names(self) -> list[str]:
|
||||
|
@ -96,7 +126,7 @@ class DaneRenewableCert(RenewableCert):
|
|||
archive, and link it as the current (latest) certificate.
|
||||
"""
|
||||
archive_path = Path(self.archive_dir)
|
||||
for pem_path in self.all_upcomings_in_place:
|
||||
for pem_path in self.all_upcomings_in_place.values():
|
||||
new_path = archive_path.joinpath(pem_path.name)
|
||||
if pem_path.is_symlink():
|
||||
# It's a private key symlinked to a former one (pinned)
|
||||
|
@ -193,3 +223,36 @@ class DaneRenewableCert(RenewableCert):
|
|||
targets=targets, new_cert=new_cert, new_chain=new_chain)
|
||||
self.__update_configfile()
|
||||
self.__reset_cache()
|
||||
|
||||
@cached_property
|
||||
def __pubcert(self) -> Certificate:
|
||||
data = Path(self.cert_path).read_bytes()
|
||||
return load_pem_x509_certificate(data=data)
|
||||
|
||||
@cached_property
|
||||
def __upcoming_pubcert(self) -> Certificate | None:
|
||||
if not self.all_upcomings_in_place:
|
||||
return
|
||||
data = Path(self.all_upcomings_in_place['cert']).read_bytes()
|
||||
return load_pem_x509_certificate(data=data)
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_tlsa_recordtypes(
|
||||
self, usage: TlsaUsage, selector: TlsaSelector,
|
||||
matching_type: TlsaMatchingType) -> list[TlsaRecordType]:
|
||||
'Return a list of generated `TlsaRecordType`s.'
|
||||
result = list[TlsaRecordType]()
|
||||
data = get_tlsa_bytes(
|
||||
cert=self.__pubcert, selector=selector,
|
||||
matching_type=matching_type)
|
||||
result.append(TlsaRecordType(
|
||||
usage=usage, selector=selector, matching_type=matching_type,
|
||||
data=data))
|
||||
if self.__upcoming_pubcert:
|
||||
data = get_tlsa_bytes(
|
||||
cert=self.__upcoming_pubcert, selector=selector,
|
||||
matching_type=matching_type)
|
||||
result.append(TlsaRecordType(
|
||||
usage=usage, selector=selector, matching_type=matching_type,
|
||||
data=data))
|
||||
return result
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
from .common import (
|
||||
LITERAL_TCP_OR_UDP, LITERAL_TSIG_ALGORITHMS, TlsaMatchingType,
|
||||
TlsaSelector, TlsaUsage)
|
||||
from .common import LITERAL_TCP_OR_UDP, LITERAL_TSIG_ALGORITHMS
|
||||
|
||||
|
||||
class DefaultsTlsaPortDict(TypedDict):
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
from hashlib import sha256, sha512
|
||||
|
||||
from cryptography.hazmat.primitives._serialization import (
|
||||
Encoding, PublicFormat)
|
||||
from cryptography.x509 import (
|
||||
BasicConstraints, Certificate, load_pem_x509_certificate)
|
||||
|
||||
|
||||
def sha_digests(content: bytes) -> tuple[str, str]:
|
||||
'Generate hexadecimal SHA256 and SHA512 hashes for some data.'
|
||||
hash_sha256 = sha256()
|
||||
hash_sha256.update(content)
|
||||
hash_sha512 = sha512()
|
||||
hash_sha512.update(content)
|
||||
return hash_sha256.hexdigest(), hash_sha512.hexdigest()
|
||||
|
||||
|
||||
def read_x509_cert(filename: str) -> Certificate:
|
||||
'Read x509 certificate from file.'
|
||||
with open(filename, 'rb') as f:
|
||||
return load_pem_x509_certificate(f.read())
|
||||
|
||||
|
||||
def dane_tlsa_records(cert: Certificate) -> list[str]:
|
||||
"""
|
||||
Return list of TLSA record data for the certificate.
|
||||
|
||||
Args:
|
||||
cert: x509 certificate.
|
||||
"""
|
||||
bc = cert.extensions.get_extension_for_class(BasicConstraints).value
|
||||
# DANE-TA=2, DANE-EE=3
|
||||
usage = 2 if bc.ca else 3
|
||||
public_key = cert.public_key().public_bytes(
|
||||
format=PublicFormat.SubjectPublicKeyInfo, encoding=Encoding.DER)
|
||||
h_sha256, h_sha512 = sha_digests(content=public_key)
|
||||
return [f'{usage} 1 1 {h_sha256}', f'{usage} 1 2 {h_sha512}']
|
|
@ -3,18 +3,24 @@ from __future__ import annotations
|
|||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
from itertools import product
|
||||
|
||||
from certbot.errors import ConfigurationError
|
||||
from dns.name import Name
|
||||
from dns.name import from_text as name_from_text
|
||||
from dns.nameserver import Do53Nameserver
|
||||
from dns.rdatatype import TLSA
|
||||
from dns.rdtypes.ANY.TLSA import TLSA as RdTypeTlsa
|
||||
from dns.resolver import Resolver
|
||||
from dns.tsig import Key
|
||||
from dns.tsigkeyring import from_text as tsig_from_text
|
||||
from dns.update import Update
|
||||
from ktools.cache.functional import memoized_method
|
||||
|
||||
from ..cert import DaneCert
|
||||
from ..config import (
|
||||
AdoptedCertConfig, AdoptedCertHostitemRecordsitemConfig,
|
||||
AdoptedCertHostitemServeritemConfig, Configuration)
|
||||
from ..config_types.cert import DaneRenewableCert
|
||||
from ..config_types.common import (
|
||||
TlsaMatchingType, TlsaProtocol, TlsaSelector, TlsaUsage)
|
||||
from .record_type import TlsaRecordType
|
||||
|
@ -91,12 +97,30 @@ class _DnsServerInfo(object):
|
|||
result.append(_DnsServerInfo(ip=item.ip, tsig=tsig))
|
||||
return result
|
||||
|
||||
@cached_property
|
||||
def resolver(self) -> Resolver:
|
||||
'A cached resolver.'
|
||||
my_nameserver = Do53Nameserver(address=str(self))
|
||||
resolver = Resolver(configure=False)
|
||||
resolver.nameservers = [my_nameserver]
|
||||
return resolver
|
||||
|
||||
def resolve(self, fqdn: str) -> set[TlsaRecordType]:
|
||||
qname = name_from_text(text=fqdn)
|
||||
answer = self.resolver.resolve(qname=qname, rdtype=TLSA)
|
||||
response_set = set[TlsaRecordType]()
|
||||
item: RdTypeTlsa
|
||||
for item in answer: # type: ignore
|
||||
record = TlsaRecordType.from_rdtype(item=item)
|
||||
response_set.add(record)
|
||||
return response_set
|
||||
|
||||
|
||||
_serverinfo_cache = dict[Configuration, list[_DnsServerInfo]]()
|
||||
|
||||
|
||||
@dataclass
|
||||
class _TlsaRecordsInfo(object):
|
||||
class _TlsaRecordsConfig(object):
|
||||
'Gathered `runtime.yaml` and `config.yaml` ports.'
|
||||
protocol: TlsaProtocol
|
||||
number: int
|
||||
|
@ -108,35 +132,52 @@ class _TlsaRecordsInfo(object):
|
|||
def from_adopted(
|
||||
records: list[AdoptedCertHostitemRecordsitemConfig],
|
||||
config: Configuration
|
||||
) -> list[_TlsaRecordsInfo]:
|
||||
) -> list[_TlsaRecordsConfig]:
|
||||
"""
|
||||
Return prepared records configuration by merging from the
|
||||
adopted and the default configuration.
|
||||
"""
|
||||
result = list[_TlsaRecordsInfo]()
|
||||
result = list[_TlsaRecordsConfig]()
|
||||
def_tlsa = config.defaults.tlsa
|
||||
for item in records:
|
||||
usage = item.usage or def_tlsa.usage
|
||||
selector = item.selector or def_tlsa.selector
|
||||
matching_type = item.matching_type or def_tlsa.matching_type
|
||||
result.append(_TlsaRecordsInfo(
|
||||
result.append(_TlsaRecordsConfig(
|
||||
protocol=item.protocol, number=item.number, usage=usage,
|
||||
selector=selector, matching_type=matching_type))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_defaults(config: Configuration) -> list[_TlsaRecordsInfo]:
|
||||
def get_defaults(config: Configuration) -> list[_TlsaRecordsConfig]:
|
||||
'Return configuration defaults.'
|
||||
result = list[_TlsaRecordsInfo]()
|
||||
result = list[_TlsaRecordsConfig]()
|
||||
def_tlsa = config.defaults.tlsa
|
||||
for item in def_tlsa.ports:
|
||||
result.append(_TlsaRecordsInfo(
|
||||
result.append(_TlsaRecordsConfig(
|
||||
protocol=TlsaProtocol.from_config_literal(data=item.protocol),
|
||||
number=item.number, usage=def_tlsa.usage,
|
||||
selector=def_tlsa.selector,
|
||||
matching_type=def_tlsa.matching_type))
|
||||
return result
|
||||
|
||||
@cached_property
|
||||
def prefix(self) -> str:
|
||||
return f'_{self.number}.{self.protocol.value}'
|
||||
|
||||
def required_records_for_cert(self, cert: DaneCert) -> set[TlsaRecordType]:
|
||||
"""
|
||||
Return the currently required `TlsaRecordType`s for the passed
|
||||
`DaneCert`.
|
||||
"""
|
||||
result = set[TlsaRecordType]()
|
||||
iterator = product(self.usage, self.selector, self.matching_type)
|
||||
for usage, selector, matching_type in iterator:
|
||||
result.update(cert.get_tlsa_recordtypes(
|
||||
usage=usage, selector=selector,
|
||||
matching_type=matching_type))
|
||||
return result
|
||||
|
||||
|
||||
@dataclass
|
||||
class HostnameConfig(object):
|
||||
|
@ -145,7 +186,7 @@ class HostnameConfig(object):
|
|||
zone: str
|
||||
ttl: int
|
||||
servers: list[_DnsServerInfo]
|
||||
records: list[_TlsaRecordsInfo]
|
||||
records: list[_TlsaRecordsConfig]
|
||||
|
||||
@staticmethod
|
||||
def from_fqdn(fqdn: str, config: Configuration) -> HostnameConfig:
|
||||
|
@ -158,12 +199,12 @@ class HostnameConfig(object):
|
|||
return HostnameConfig(
|
||||
name=host, zone=zone, ttl=config.defaults.tlsa.ttl,
|
||||
servers=_DnsServerInfo.get_defaults(config=config),
|
||||
records=_TlsaRecordsInfo.get_defaults(config=config))
|
||||
records=_TlsaRecordsConfig.get_defaults(config=config))
|
||||
|
||||
@staticmethod
|
||||
def from_adoptedhosts(
|
||||
adopted: AdoptedCertConfig, config: Configuration,
|
||||
cert: DaneRenewableCert
|
||||
cert: DaneCert
|
||||
) -> list[HostnameConfig]:
|
||||
"""
|
||||
Return a list of `LiveupdateConfig` from adopted runtime
|
||||
|
@ -180,14 +221,23 @@ class HostnameConfig(object):
|
|||
servers = _DnsServerInfo.from_adopted(
|
||||
servers=item.servers, config=config)
|
||||
if item.records is None:
|
||||
records = _TlsaRecordsInfo.get_defaults(config=config)
|
||||
records = _TlsaRecordsConfig.get_defaults(config=config)
|
||||
else:
|
||||
records = _TlsaRecordsInfo.from_adopted(
|
||||
records = _TlsaRecordsConfig.from_adopted(
|
||||
records=item.records, config=config)
|
||||
result.append(HostnameConfig(
|
||||
name=item.name, zone=zone, ttl=ttl, servers=servers,
|
||||
records=records))
|
||||
return result
|
||||
|
||||
def tlsa_records(self) -> list[TlsaRecordType]:
|
||||
pass
|
||||
def get_tlsa_records(
|
||||
self, cert: DaneCert) -> dict[str, set[TlsaRecordType]]:
|
||||
'Return TLSA records for the configuration here.'
|
||||
result = dict[str, set[TlsaRecordType]]()
|
||||
for record in self.records:
|
||||
name = record.prefix
|
||||
if self.name != '.':
|
||||
name += f'.{self.name}'
|
||||
name += f'.{self.zone}'
|
||||
result[name] = record.required_records_for_cert(cert=cert)
|
||||
return result
|
||||
|
|
|
@ -16,22 +16,22 @@ class TlsaRecordType(object):
|
|||
usage: TlsaUsage
|
||||
selector: TlsaSelector
|
||||
matching_type: TlsaMatchingType
|
||||
cert: bytes
|
||||
data: bytes
|
||||
|
||||
@staticmethod
|
||||
def from_rdtype(item: TLSA):
|
||||
return TlsaRecordType(
|
||||
usage=TlsaUsage(value=item.usage),
|
||||
selector=TlsaSelector(value=item.selector),
|
||||
matching_type=TlsaMatchingType(value=item.mtype), cert=item.cert)
|
||||
matching_type=TlsaMatchingType(value=item.mtype), data=item.cert)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, TlsaRecordType):
|
||||
return self.usage == other.usage \
|
||||
and self.selector == other.selector \
|
||||
and self.matching_type == other.matching_type \
|
||||
and self.cert == other.cert
|
||||
and self.data == other.data
|
||||
return False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.usage, self.selector, self.matching_type, self.cert))
|
||||
return hash((self.usage, self.selector, self.matching_type, self.data))
|
||||
|
|
|
@ -1,14 +1,31 @@
|
|||
from daneupdate.utils.config_types.cert import DaneRenewableCert
|
||||
from daneupdate.utils.cert import DaneCert
|
||||
from daneupdate.utils.tlsa.record_type import TlsaRecordType
|
||||
from dns.name import from_text as name_from_text
|
||||
from dns.rdatatype import TLSA
|
||||
from dns.rdtypes.ANY.TLSA import TLSA as RdTypeTlsa
|
||||
from dns.resolver import Resolver
|
||||
|
||||
from ..config import Configuration
|
||||
from .liveupdate_config import HostnameConfig
|
||||
|
||||
|
||||
def _get_records_from_ns(
|
||||
fqdn: str, resolver: Resolver) -> set[TlsaRecordType]:
|
||||
qname = name_from_text(text='_25._tcp.mail.weberdns.de.')
|
||||
answer = resolver.resolve(qname=qname, rdtype=TLSA)
|
||||
response_set = set[TlsaRecordType]()
|
||||
item: RdTypeTlsa
|
||||
for item in answer:
|
||||
record = TlsaRecordType.from_rdtype(item=item)
|
||||
response_set.add(record)
|
||||
return response_set
|
||||
|
||||
|
||||
class TlsaUpdater(object):
|
||||
'Updating TLSA records for an deployed and/or upcoming certificate.'
|
||||
|
||||
def __init__(
|
||||
self, config: Configuration, cert: DaneRenewableCert,
|
||||
self, config: Configuration, cert: DaneCert,
|
||||
certconfig_path: str
|
||||
):
|
||||
self._adopted_config = config.runtime.adopted[certconfig_path]
|
||||
|
@ -28,13 +45,16 @@ class TlsaUpdater(object):
|
|||
HostnameConfig.from_fqdn(fqdn=fqdn, config=self._config)
|
||||
for fqdn in self._cert.names()]
|
||||
|
||||
def _get_missing_records(self):
|
||||
pass
|
||||
|
||||
def process(self):
|
||||
if self._config.defaults.method == 'dns-update':
|
||||
for hostname_config in self._get_hostname_configs():
|
||||
for serverinfo in hostname_config.servers:
|
||||
update = serverinfo.get_zoneupdate(
|
||||
zone=hostname_config.zone)
|
||||
update.replace
|
||||
tlsa_records = \
|
||||
hostname_config.get_tlsa_records(cert=self._cert)
|
||||
for fqdn, records_required in tlsa_records.items():
|
||||
for server in hostname_config.servers:
|
||||
records_existing = server.resolve(fqdn=fqdn)
|
||||
if records_existing != records_required:
|
||||
pass
|
||||
# update = serverinfo.get_zoneupdate(
|
||||
# zone=hostname_config.zone)
|
||||
# update.replace
|
||||
|
|
Loading…
Reference in New Issue