Compare commits

..

2 commits

Author SHA1 Message Date
5b0b2e77ce
wip 2023-08-01 18:58:37 +02:00
2256f8b384
wip 2023-08-01 18:08:54 +02:00
8 changed files with 115 additions and 143 deletions

View file

@ -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())

View file

@ -0,0 +1,2 @@
; ${type} ${key_id}
${record}

View 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']}

View file

@ -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'] = {
'source': 'defaults',
'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'] = {}
actions['named-checkconf'] = {

View file

@ -39,7 +39,7 @@ defaults = {
'zones': {},
},
},
'zones': set(),
'zones': {},
},
'nftables': {
'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(
'bind/type',
'bind/master_ip',

View file

@ -1,5 +1,7 @@
from os.path import join, exists
from re import sub
from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from base64 import b64decode

View file

@ -1,5 +1,3 @@
#!/usr/bin/env python3
# https://medium.com/iocscan/how-dnssec-works-9c652257be0
# https://de.wikipedia.org/wiki/RRSIG_Resource_Record
# 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 struct import pack, unpack
from hashlib import sha1, sha256
from json import dumps
from cache_to_disk import cache_to_disk
def long_to_base64(n):
return urlsafe_b64encode(int_to_bytes(n, None)).decode()
zone = argv[1]
repo = Repository(dirname(dirname(realpath(__file__))))
flags = 256
protocol = 3
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://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(
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
exponent2 = privkey.private_numbers().dmq1
coefficient = privkey.private_numbers().iqmp
flags = 256 if salt == 'zsk' else 257
dnskey = ''.join(privkey.public_key().public_bytes(
crypto_serialization.Encoding.PEM,
@ -55,7 +49,7 @@ def generate_signing_key_pair(zone, salt):
return {
'dnskey': dnskey,
'dnskey_record': f'{zone}. IN DNSKEY {flags} {protocol} {algorithm} {dnskey}',
'privkey': privkey,
'key_id': _calc_keyid(flags, protocol, algorithm, dnskey),
'privkey_file': {
'Private-key-format': 'v1.3',
'Algorithm': f'{algorithm} ({algorithm_name})',
@ -108,88 +102,23 @@ def _calc_keyid(flags, protocol, algorithm, dnskey):
return ((cnt & 0xFFFF) + (cnt >> 16)) & 0xFFFF
def dnskey_to_ds(zone, flags, protocol, algorithm, dnskey):
keyid = _calc_keyid(flags, protocol, algorithm, dnskey)
def dnskey_to_ds(zone, flags, protocol, algorithm, dnskey, key_id):
ds = _calc_ds(zone, flags, protocol, algorithm, dnskey)
return[
f"{zone}. IN DS {str(keyid)} {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)} 1 {ds['sha1'].lower()}",
f"{zone}. IN DS {str(key_id)} {str(algorithm)} 2 {ds['sha256'].lower()}",
]
# Result
#@cache_to_disk(30)
def generate_dnssec_for_zone(zone):
zsk_data = generate_signing_key_pair(zone, salt='zsk')
ksk_data = generate_signing_key_pair(zone, salt='ksk')
ds_records = dnskey_to_ds(zone, flags, protocol, algorithm, ksk_data['dnskey'])
def generate_dnssec_for_zone(zone, node):
zsk_data = generate_signing_key_pair(zone, salt='zsk', repo=node.repo)
ksk_data = generate_signing_key_pair(zone, salt='ksk', repo=node.repo)
ds_records = dnskey_to_ds(zone, flags, protocol, algorithm, ksk_data['dnskey'], ksk_data['key_id'])
return {
'zsk_data': zsk_data,
'ksk_data': ksk_data,
'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,
# )

View file

@ -39,20 +39,20 @@
'hostname': 'resolver.name',
'acme_zone': 'acme.sublimity.de',
'zones': {
'sublimity.de',
'freibrief.net',
'nadenau.net',
'naeder.net',
'wettengl.net',
'wingl.de',
'woodpipe.de',
'ckn.li',
'islamicstate.eu',
'hausamsilberberg.de',
'wiegand.tel',
'lonercrew.io',
'left4.me',
'elimu-kwanza.de',
'sublimity.de': {},
'freibrief.net': {},
'nadenau.net': {},
'naeder.net': {},
'wettengl.net': {},
'wingl.de': {},
'woodpipe.de': {},
'ckn.li': {},
'islamicstate.eu': {},
'hausamsilberberg.de': {},
'wiegand.tel': {},
'lonercrew.io': {},
'left4.me': {},
'elimu-kwanza.de': {},
},
},
'dns': {