Compare commits
2 commits
ef2cff1510
...
5b0b2e77ce
Author | SHA1 | Date | |
---|---|---|---|
5b0b2e77ce | |||
2256f8b384 |
8 changed files with 115 additions and 143 deletions
47
bin/test
47
bin/test
|
@ -1,47 +0,0 @@
|
||||||
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())
|
|
2
bundles/bind/files/dnssec.key
Normal file
2
bundles/bind/files/dnssec.key
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
; ${type} ${key_id}
|
||||||
|
${record}
|
13
bundles/bind/files/dnssec.private
Normal file
13
bundles/bind/files/dnssec.private
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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,6 +23,20 @@ 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': [
|
||||||
|
@ -132,6 +146,49 @@ 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': set(),
|
'zones': {},
|
||||||
},
|
},
|
||||||
'nftables': {
|
'nftables': {
|
||||||
'input': {
|
'input': {
|
||||||
|
@ -61,6 +61,22 @@ 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,5 +1,7 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#!/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/
|
||||||
|
@ -13,16 +11,11 @@ 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
|
||||||
|
@ -33,7 +26,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):
|
def generate_signing_key_pair(zone, salt, repo):
|
||||||
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)))
|
||||||
)
|
)
|
||||||
|
@ -46,6 +39,7 @@ def generate_signing_key_pair(zone, salt):
|
||||||
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,
|
||||||
|
@ -55,7 +49,7 @@ def generate_signing_key_pair(zone, salt):
|
||||||
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}',
|
||||||
'privkey': privkey,
|
'key_id': _calc_keyid(flags, protocol, algorithm, dnskey),
|
||||||
'privkey_file': {
|
'privkey_file': {
|
||||||
'Private-key-format': 'v1.3',
|
'Private-key-format': 'v1.3',
|
||||||
'Algorithm': f'{algorithm} ({algorithm_name})',
|
'Algorithm': f'{algorithm} ({algorithm_name})',
|
||||||
|
@ -108,88 +102,23 @@ 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):
|
def dnskey_to_ds(zone, flags, protocol, algorithm, dnskey, key_id):
|
||||||
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(keyid)} {str(algorithm)} 1 {ds['sha1'].lower()}",
|
f"{zone}. IN DS {str(key_id)} {str(algorithm)} 1 {ds['sha1'].lower()}",
|
||||||
f"{zone}. IN DS {str(keyid)} {str(algorithm)} 2 {ds['sha256'].lower()}",
|
f"{zone}. IN DS {str(key_id)} {str(algorithm)} 2 {ds['sha256'].lower()}",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Result
|
# Result
|
||||||
|
|
||||||
#@cache_to_disk(30)
|
def generate_dnssec_for_zone(zone, node):
|
||||||
def generate_dnssec_for_zone(zone):
|
zsk_data = generate_signing_key_pair(zone, salt='zsk', repo=node.repo)
|
||||||
zsk_data = generate_signing_key_pair(zone, salt='zsk')
|
ksk_data = generate_signing_key_pair(zone, salt='ksk', repo=node.repo)
|
||||||
ksk_data = generate_signing_key_pair(zone, salt='ksk')
|
ds_records = dnskey_to_ds(zone, flags, protocol, algorithm, ksk_data['dnskey'], ksk_data['key_id'])
|
||||||
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,
|
|
||||||
# )
|
|
|
@ -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