Compare commits
1 commit
5b0b2e77ce
...
ef2cff1510
Author | SHA1 | Date | |
---|---|---|---|
ef2cff1510 |
8 changed files with 143 additions and 115 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# https://medium.com/iocscan/how-dnssec-works-9c652257be0
|
# https://medium.com/iocscan/how-dnssec-works-9c652257be0
|
||||||
# https://de.wikipedia.org/wiki/RRSIG_Resource_Record
|
# https://de.wikipedia.org/wiki/RRSIG_Resource_Record
|
||||||
# https://metebalci.com/blog/a-minimum-complete-tutorial-of-dnssec/
|
# https://metebalci.com/blog/a-minimum-complete-tutorial-of-dnssec/
|
||||||
|
@ -11,11 +13,16 @@ from cryptography.utils import int_to_bytes
|
||||||
from cryptography.hazmat.primitives import serialization as crypto_serialization
|
from cryptography.hazmat.primitives import serialization as crypto_serialization
|
||||||
from struct import pack, unpack
|
from struct import pack, unpack
|
||||||
from hashlib import sha1, sha256
|
from hashlib import sha1, sha256
|
||||||
|
from json import dumps
|
||||||
|
from cache_to_disk import cache_to_disk
|
||||||
|
|
||||||
|
|
||||||
def long_to_base64(n):
|
def long_to_base64(n):
|
||||||
return urlsafe_b64encode(int_to_bytes(n, None)).decode()
|
return urlsafe_b64encode(int_to_bytes(n, None)).decode()
|
||||||
|
|
||||||
|
zone = argv[1]
|
||||||
|
repo = Repository(dirname(dirname(realpath(__file__))))
|
||||||
|
|
||||||
flags = 256
|
flags = 256
|
||||||
protocol = 3
|
protocol = 3
|
||||||
algorithm = 8
|
algorithm = 8
|
||||||
|
@ -26,7 +33,7 @@ algorithm_name = 'RSASHA256'
|
||||||
# https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers
|
# https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers
|
||||||
# https://crypto.stackexchange.com/a/21104
|
# https://crypto.stackexchange.com/a/21104
|
||||||
|
|
||||||
def generate_signing_key_pair(zone, salt, repo):
|
def generate_signing_key_pair(zone, salt):
|
||||||
privkey = repo.libs.rsa.generate_deterministic_rsa_private_key(
|
privkey = repo.libs.rsa.generate_deterministic_rsa_private_key(
|
||||||
b64decode(str(repo.vault.random_bytes_as_base64_for(f'dnssec {salt} ' + zone)))
|
b64decode(str(repo.vault.random_bytes_as_base64_for(f'dnssec {salt} ' + zone)))
|
||||||
)
|
)
|
||||||
|
@ -39,7 +46,6 @@ def generate_signing_key_pair(zone, salt, repo):
|
||||||
exponent1 = privkey.private_numbers().dmp1
|
exponent1 = privkey.private_numbers().dmp1
|
||||||
exponent2 = privkey.private_numbers().dmq1
|
exponent2 = privkey.private_numbers().dmq1
|
||||||
coefficient = privkey.private_numbers().iqmp
|
coefficient = privkey.private_numbers().iqmp
|
||||||
flags = 256 if salt == 'zsk' else 257
|
|
||||||
|
|
||||||
dnskey = ''.join(privkey.public_key().public_bytes(
|
dnskey = ''.join(privkey.public_key().public_bytes(
|
||||||
crypto_serialization.Encoding.PEM,
|
crypto_serialization.Encoding.PEM,
|
||||||
|
@ -49,7 +55,7 @@ def generate_signing_key_pair(zone, salt, repo):
|
||||||
return {
|
return {
|
||||||
'dnskey': dnskey,
|
'dnskey': dnskey,
|
||||||
'dnskey_record': f'{zone}. IN DNSKEY {flags} {protocol} {algorithm} {dnskey}',
|
'dnskey_record': f'{zone}. IN DNSKEY {flags} {protocol} {algorithm} {dnskey}',
|
||||||
'key_id': _calc_keyid(flags, protocol, algorithm, dnskey),
|
'privkey': privkey,
|
||||||
'privkey_file': {
|
'privkey_file': {
|
||||||
'Private-key-format': 'v1.3',
|
'Private-key-format': 'v1.3',
|
||||||
'Algorithm': f'{algorithm} ({algorithm_name})',
|
'Algorithm': f'{algorithm} ({algorithm_name})',
|
||||||
|
@ -102,23 +108,88 @@ def _calc_keyid(flags, protocol, algorithm, dnskey):
|
||||||
|
|
||||||
return ((cnt & 0xFFFF) + (cnt >> 16)) & 0xFFFF
|
return ((cnt & 0xFFFF) + (cnt >> 16)) & 0xFFFF
|
||||||
|
|
||||||
def dnskey_to_ds(zone, flags, protocol, algorithm, dnskey, key_id):
|
def dnskey_to_ds(zone, flags, protocol, algorithm, dnskey):
|
||||||
|
keyid = _calc_keyid(flags, protocol, algorithm, dnskey)
|
||||||
ds = _calc_ds(zone, flags, protocol, algorithm, dnskey)
|
ds = _calc_ds(zone, flags, protocol, algorithm, dnskey)
|
||||||
|
|
||||||
return[
|
return[
|
||||||
f"{zone}. IN DS {str(key_id)} {str(algorithm)} 1 {ds['sha1'].lower()}",
|
f"{zone}. IN DS {str(keyid)} {str(algorithm)} 1 {ds['sha1'].lower()}",
|
||||||
f"{zone}. IN DS {str(key_id)} {str(algorithm)} 2 {ds['sha256'].lower()}",
|
f"{zone}. IN DS {str(keyid)} {str(algorithm)} 2 {ds['sha256'].lower()}",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Result
|
# Result
|
||||||
|
|
||||||
def generate_dnssec_for_zone(zone, node):
|
#@cache_to_disk(30)
|
||||||
zsk_data = generate_signing_key_pair(zone, salt='zsk', repo=node.repo)
|
def generate_dnssec_for_zone(zone):
|
||||||
ksk_data = generate_signing_key_pair(zone, salt='ksk', repo=node.repo)
|
zsk_data = generate_signing_key_pair(zone, salt='zsk')
|
||||||
ds_records = dnskey_to_ds(zone, flags, protocol, algorithm, ksk_data['dnskey'], ksk_data['key_id'])
|
ksk_data = generate_signing_key_pair(zone, salt='ksk')
|
||||||
|
ds_records = dnskey_to_ds(zone, flags, protocol, algorithm, ksk_data['dnskey'])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'zsk_data': zsk_data,
|
'zsk_data': zsk_data,
|
||||||
'ksk_data': ksk_data,
|
'ksk_data': ksk_data,
|
||||||
'ds_records': ds_records,
|
'ds_records': ds_records,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print(
|
||||||
|
generate_dnssec_for_zone(zone),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# #########################
|
||||||
|
|
||||||
|
# from dns import rrset, rdatatype, rdata
|
||||||
|
# from dns.rdataclass import IN
|
||||||
|
# from dns.dnssec import sign, make_dnskey
|
||||||
|
# from dns.name import Name
|
||||||
|
# from dns.rdtypes.IN.A import A
|
||||||
|
|
||||||
|
# data = generate_dnssec_for_zone(zone)
|
||||||
|
# zone_name = Name(f'{zone}.'.split('.'))
|
||||||
|
# assert zone_name.is_absolute()
|
||||||
|
|
||||||
|
# # rrset = rrset.from_text_list(
|
||||||
|
# # name=Name(['test']).derelativize(zone_name),
|
||||||
|
# # origin=zone_name,
|
||||||
|
# # relativize=False,
|
||||||
|
# # ttl=60,
|
||||||
|
# # rdclass=IN,
|
||||||
|
# # rdtype=rdatatype.from_text('A'),
|
||||||
|
# # text_rdatas=[
|
||||||
|
# # '100.2.3.4',
|
||||||
|
# # '10.0.0.55',
|
||||||
|
# # ],
|
||||||
|
# # )
|
||||||
|
|
||||||
|
# rrset = rrset.from_rdata_list(
|
||||||
|
# name=Name(['test']).derelativize(zone_name),
|
||||||
|
# ttl=60,
|
||||||
|
# rdatas=[
|
||||||
|
# rdata.from_text(
|
||||||
|
# rdclass=IN,
|
||||||
|
# rdtype=rdatatype.from_text('A'),
|
||||||
|
# origin=zone_name,
|
||||||
|
# tok='1.2.3.4',
|
||||||
|
# relativize=False,
|
||||||
|
# ),
|
||||||
|
# A(IN, rdatatype.from_text('A'), '10.20.30.40')
|
||||||
|
# ],
|
||||||
|
# )
|
||||||
|
|
||||||
|
# # for e in rrset:
|
||||||
|
# # print(e.is_absolute())
|
||||||
|
|
||||||
|
# dnskey = make_dnskey(
|
||||||
|
# public_key=data['zsk_data']['privkey'].public_key(),
|
||||||
|
# algorithm=algorithm,
|
||||||
|
# flags=flags,
|
||||||
|
# protocol=protocol,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# sign(
|
||||||
|
# rrset=rrset,
|
||||||
|
# private_key=data['zsk_data']['privkey'],
|
||||||
|
# signer=Name(f'{zone}.'),
|
||||||
|
# dnskey=dnskey,
|
||||||
|
# lifetime=99999,
|
||||||
|
# )
|
47
bin/test
Executable file
47
bin/test
Executable file
|
@ -0,0 +1,47 @@
|
||||||
|
import dns.zone
|
||||||
|
import dns.rdatatype
|
||||||
|
import dns.rdataclass
|
||||||
|
import dns.dnssec
|
||||||
|
|
||||||
|
# Define the zone name and domain names
|
||||||
|
zone_name = 'example.com.'
|
||||||
|
a_name = 'www.example.com.'
|
||||||
|
txt_name = 'example.com.'
|
||||||
|
mx_name = 'example.com.'
|
||||||
|
|
||||||
|
# Define the DNSKEY algorithm and size
|
||||||
|
algorithm = 8
|
||||||
|
key_size = 2048
|
||||||
|
|
||||||
|
# Generate the DNSSEC key pair
|
||||||
|
keypair = dns.dnssec.make_dnskey(algorithm, key_size)
|
||||||
|
|
||||||
|
# Create the zone
|
||||||
|
zone = dns.zone.Zone(origin=zone_name)
|
||||||
|
|
||||||
|
# Add A record to zone
|
||||||
|
a_rrset = zone.get_rdataset(a_name, rdtype=dns.rdatatype.A, create=True)
|
||||||
|
a_rrset.add(dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.1')
|
||||||
|
|
||||||
|
# Add TXT record to zone
|
||||||
|
txt_rrset = zone.get_rdataset(txt_name, rdtype=dns.rdatatype.TXT, create=True)
|
||||||
|
txt_rrset.add(dns.rdataclass.IN, dns.rdatatype.TXT, 'Hello, world!')
|
||||||
|
|
||||||
|
# Add MX record to zone
|
||||||
|
mx_rrset = zone.get_rdataset(mx_name, rdtype=dns.rdatatype.MX, create=True)
|
||||||
|
mx_rrset.add(dns.rdataclass.IN, dns.rdatatype.MX, '10 mail.example.com.')
|
||||||
|
|
||||||
|
# Create the DNSKEY record for the zone
|
||||||
|
key_name = f'{keypair.name}-K{keypair.fingerprint()}'
|
||||||
|
dnskey_rrset = dns.rrset.RRset(name=keypair.name, rdclass=dns.rdataclass.IN, rdtype=dns.rdatatype.DNSKEY)
|
||||||
|
dnskey_rrset.ttl = 86400
|
||||||
|
dnskey_rrset.add(dns.rdataclass.IN, dns.rdatatype.DNSKEY, keypair.key, key_name=key_name)
|
||||||
|
|
||||||
|
# Add the DNSKEY record to the zone
|
||||||
|
zone.replace_rdataset(keypair.name, dnskey_rrset)
|
||||||
|
|
||||||
|
# Sign the zone with the DNSSEC key pair
|
||||||
|
dns.dnssec.sign_zone(zone, keypair, inception=0, expiration=3600)
|
||||||
|
|
||||||
|
# Print the resulting zone with the RRSIG records
|
||||||
|
print(zone.to_text())
|
|
@ -1,2 +0,0 @@
|
||||||
; ${type} ${key_id}
|
|
||||||
${record}
|
|
|
@ -1,13 +0,0 @@
|
||||||
Private-key-format: ${data['Private-key-format']}
|
|
||||||
Algorithm: ${data['Algorithm']}
|
|
||||||
Modulus: ${data['Modulus']}
|
|
||||||
PublicExponent: ${data['PublicExponent']}
|
|
||||||
PrivateExponent: ${data['PrivateExponent']}
|
|
||||||
Prime1: ${data['Prime1']}
|
|
||||||
Prime2: ${data['Prime2']}
|
|
||||||
Exponent1: ${data['Exponent1']}
|
|
||||||
Exponent2: ${data['Exponent2']}
|
|
||||||
Coefficient: ${data['Coefficient']}
|
|
||||||
Created: ${data['Created']}
|
|
||||||
Publish: ${data['Publish']}
|
|
||||||
Activate: ${data['Activate']}
|
|
|
@ -23,20 +23,6 @@ directories[f'/var/lib/bind'] = {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
directories[f'/var/cache/bind/keys'] = {
|
|
||||||
'group': 'bind',
|
|
||||||
'purge': True,
|
|
||||||
'needs': [
|
|
||||||
'pkg_apt:bind9',
|
|
||||||
],
|
|
||||||
'needed_by': [
|
|
||||||
'svc_systemd:bind9',
|
|
||||||
],
|
|
||||||
'triggers': [
|
|
||||||
'svc_systemd:bind9:restart',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/etc/default/bind9'] = {
|
files['/etc/default/bind9'] = {
|
||||||
'source': 'defaults',
|
'source': 'defaults',
|
||||||
'needed_by': [
|
'needed_by': [
|
||||||
|
@ -146,49 +132,6 @@ for view_name, view_conf in master_node.metadata.get('bind/views').items():
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for zone_name, zone_conf in master_node.metadata.get('bind/zones').items():
|
|
||||||
for sk in ('zsk_data', 'ksk_data'):
|
|
||||||
data = zone_conf['dnssec'][sk]
|
|
||||||
|
|
||||||
files[f"/var/cache/bind/keys/K{zone_name}.+008+{data['key_id']}.private"] = {
|
|
||||||
'content_type': 'mako',
|
|
||||||
'source': 'dnssec.private',
|
|
||||||
'context': {
|
|
||||||
'data': data['privkey_file'],
|
|
||||||
},
|
|
||||||
'group': 'bind',
|
|
||||||
'needs': [
|
|
||||||
'pkg_apt:bind9',
|
|
||||||
],
|
|
||||||
'needed_by': [
|
|
||||||
'svc_systemd:bind9',
|
|
||||||
],
|
|
||||||
'triggers': [
|
|
||||||
'svc_systemd:bind9:restart',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
files[f"/var/cache/bind/keys/K{zone_name}.+008+{data['key_id']}.key"] = {
|
|
||||||
'content_type': 'mako',
|
|
||||||
'source': 'dnssec.key',
|
|
||||||
'context': {
|
|
||||||
'type': sk,
|
|
||||||
'key_id': data['key_id'],
|
|
||||||
'record': data['dnskey_record'],
|
|
||||||
},
|
|
||||||
'group': 'bind',
|
|
||||||
'needs': [
|
|
||||||
'pkg_apt:bind9',
|
|
||||||
],
|
|
||||||
'needed_by': [
|
|
||||||
'svc_systemd:bind9',
|
|
||||||
],
|
|
||||||
'triggers': [
|
|
||||||
'svc_systemd:bind9:restart',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
svc_systemd['bind9'] = {}
|
svc_systemd['bind9'] = {}
|
||||||
|
|
||||||
actions['named-checkconf'] = {
|
actions['named-checkconf'] = {
|
||||||
|
|
|
@ -39,7 +39,7 @@ defaults = {
|
||||||
'zones': {},
|
'zones': {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'zones': {},
|
'zones': set(),
|
||||||
},
|
},
|
||||||
'nftables': {
|
'nftables': {
|
||||||
'input': {
|
'input': {
|
||||||
|
@ -61,22 +61,6 @@ defaults = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'bind/zones',
|
|
||||||
)
|
|
||||||
def dnssec(metadata):
|
|
||||||
return {
|
|
||||||
'bind': {
|
|
||||||
'zones': {
|
|
||||||
zone: {
|
|
||||||
'dnssec': repo.libs.dnssec.generate_dnssec_for_zone(zone, node),
|
|
||||||
}
|
|
||||||
for zone in metadata.get('bind/zones')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'bind/type',
|
'bind/type',
|
||||||
'bind/master_ip',
|
'bind/master_ip',
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from os.path import join, exists
|
|
||||||
from re import sub
|
from re import sub
|
||||||
from cryptography.hazmat.primitives import serialization as crypto_serialization
|
from cryptography.hazmat.primitives import serialization as crypto_serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,20 +39,20 @@
|
||||||
'hostname': 'resolver.name',
|
'hostname': 'resolver.name',
|
||||||
'acme_zone': 'acme.sublimity.de',
|
'acme_zone': 'acme.sublimity.de',
|
||||||
'zones': {
|
'zones': {
|
||||||
'sublimity.de': {},
|
'sublimity.de',
|
||||||
'freibrief.net': {},
|
'freibrief.net',
|
||||||
'nadenau.net': {},
|
'nadenau.net',
|
||||||
'naeder.net': {},
|
'naeder.net',
|
||||||
'wettengl.net': {},
|
'wettengl.net',
|
||||||
'wingl.de': {},
|
'wingl.de',
|
||||||
'woodpipe.de': {},
|
'woodpipe.de',
|
||||||
'ckn.li': {},
|
'ckn.li',
|
||||||
'islamicstate.eu': {},
|
'islamicstate.eu',
|
||||||
'hausamsilberberg.de': {},
|
'hausamsilberberg.de',
|
||||||
'wiegand.tel': {},
|
'wiegand.tel',
|
||||||
'lonercrew.io': {},
|
'lonercrew.io',
|
||||||
'left4.me': {},
|
'left4.me',
|
||||||
'elimu-kwanza.de': {},
|
'elimu-kwanza.de',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'dns': {
|
'dns': {
|
||||||
|
|
Loading…
Reference in a new issue