diff --git a/bundles/bind-acme/metadata.py b/bundles/bind-acme/metadata.py new file mode 100644 index 0000000..99cf9b0 --- /dev/null +++ b/bundles/bind-acme/metadata.py @@ -0,0 +1,62 @@ +from ipaddress import ip_interface + + +@metadata_reactor.provides( + 'dns', +) +def acme_records(metadata): + domains = set() + + for other_node in repo.nodes: + for domain, conf in other_node.metadata.get('letsencrypt/domains', {}).items(): + domains.add(domain) + domains.update(conf.get('aliases', [])) + + return { + 'dns': { + f'_acme-challenge.{domain}': { + 'CNAME': {f"{domain}.{metadata.get('bind/acme_zone')}."}, + } + for domain in domains + } + } + + +@metadata_reactor.provides( + 'bind/acls/acme', + 'bind/keys/acme', + 'bind/views/external/zones', +) +def acme_zone(metadata): + allowed_ips = { + str(ip_interface(other_node.metadata.get('network/internal/ipv4')).ip) + for other_node in repo.nodes + if other_node.metadata.get('letsencrypt/domains', {}) + } + + return { + 'bind': { + 'acls': { + 'acme': { + 'key acme', + '!{ !{' + ' '.join(f'{ip};' for ip in sorted(allowed_ips)) + '}; any;}', + }, + }, + 'views': { + 'external': { + 'keys': { + 'acme': {}, + }, + 'zones': { + metadata.get('bind/acme_zone'): { + 'allow_update': { + 'acme', + }, + }, + }, + }, + }, + }, + } + +#https://lists.isc.org/pipermail/bind-users/2006-January/061051.html diff --git a/bundles/bind/files/db b/bundles/bind/files/db index 8644995..a27ba2a 100644 --- a/bundles/bind/files/db +++ b/bundles/bind/files/db @@ -11,8 +11,8 @@ $TTL 600 900 ;Negative response caching TTL ) -% for record in sorted(records, key=lambda r: (r['name'], r['type'], r['value'])): -${(record['name'] or '@').ljust(column_width('name', records))} \ +% for record in sorted(records, key=lambda r: (tuple(reversed(r['name'].split('.'))), r['type'], r['value'])): +(${(record['name'] or '@').rjust(column_width('name', records))}) \ IN \ ${record['type'].ljust(column_width('type', records))} \ % if record['type'] == 'TXT': diff --git a/bundles/bind/files/named.conf.local b/bundles/bind/files/named.conf.local index 0198d49..4bf32ba 100644 --- a/bundles/bind/files/named.conf.local +++ b/bundles/bind/files/named.conf.local @@ -1,14 +1,33 @@ -% for view in views: -acl "${view['name']}" { - ${' '.join(f'{e};' for e in view['acl'])} +# KEYS + +% for view_name, view_conf in views.items(): +% for key_name, key_conf in sorted(view_conf['keys'].items()): +key "${key_name}" { + algorithm hmac-sha512; + secret "${key_conf['token']}"; +}; +% endfor +% endfor + +# ACLS + +% for acl_name, acl_content in acls.items(): +acl "${acl_name}" { + % for ac in sorted(acl_content, key=lambda e: (not e.startswith('!'), not e.startswith('key'), e)): + ${ac}; + % endfor }; % endfor -% for view in views: -view "${view['name']}" { - match-clients { ${view['name']}; }; +# VIEWS - % if view['is_internal']: +% for view_name, view_conf in views.items(): +view "${view_name}" { + match-clients { + ${view_name}; + }; + + % if view_conf['is_internal']: recursion yes; % else: recursion no; @@ -25,13 +44,20 @@ view "${view['name']}" { 8.8.8.8; }; - % for zone in zones: - zone "${zone}" { + % for zone_name, zone_conf in sorted(view_conf['zones'].items()): + zone "${zone_name}" { type ${type}; -% if type == 'slave': + % if type == 'slave': masters { ${master_ip}; }; -% endif - file "/var/lib/bind/${view['name']}/db.${zone}"; + % endif + % if type == 'master' and zone_conf.get('allow_update', False): + allow-update { + % for allow_update in zone_conf['allow_update']: + ${allow_update}; + % endfor + }; + % endif + file "/var/lib/bind/${view_name}/db.${zone_name}"; }; % endfor diff --git a/bundles/bind/files/named.conf.options b/bundles/bind/files/named.conf.options index 7478143..a17f44b 100644 --- a/bundles/bind/files/named.conf.options +++ b/bundles/bind/files/named.conf.options @@ -3,7 +3,7 @@ options { dnssec-validation auto; listen-on-v6 { any; }; - allow-query { any; }; + allow-query { any; }; max-cache-size 30%; querylog yes; diff --git a/bundles/bind/items.py b/bundles/bind/items.py index a166f2b..a318bb4 100644 --- a/bundles/bind/items.py +++ b/bundles/bind/items.py @@ -2,18 +2,13 @@ from ipaddress import ip_address, ip_interface from datetime import datetime if node.metadata.get('bind/type') == 'master': - zones = node.metadata.get('bind/zones') - master_ip = None - slave_ips = [ - ip_interface(repo.get_node(slave).metadata.get('network/external/ipv4')).ip - for slave in node.metadata.get('bind/slaves') - ] + master_node = node else: - zones = repo.get_node(node.metadata.get('bind/master_node')).metadata.get('bind/zones') - master_ip = ip_interface(repo.get_node(node.metadata.get('bind/master_node')).metadata.get('network/external/ipv4')).ip - slave_ips = [] + master_node = repo.get_node(node.metadata.get('bind/master_node')) directories[f'/var/lib/bind'] = { + 'owner': 'bind', + 'group': 'bind', 'purge': True, 'needed_by': [ 'svc_systemd:bind9', @@ -46,11 +41,13 @@ files['/etc/bind/named.conf'] = { 'svc_systemd:bind9:restart', ], } + files['/etc/bind/named.conf.options'] = { 'content_type': 'mako', 'context': { 'type': node.metadata.get('bind/type'), - 'slave_ips': sorted(slave_ips), + 'slave_ips': node.metadata.get('bind/slave_ips', []), + 'master_ip': node.metadata.get('bind/master_ip', None), }, 'owner': 'root', 'group': 'bind', @@ -65,34 +62,22 @@ files['/etc/bind/named.conf.options'] = { ], } -views = [ - { - 'name': 'internal', - 'is_internal': True, - 'acl': [ - '127.0.0.1', - '10.0.0.0/8', - '169.254.0.0/16', - '172.16.0.0/12', - '192.168.0.0/16', - ] - }, - { - 'name': 'external', - 'is_internal': False, - 'acl': [ - 'any', - ] - }, -] - files['/etc/bind/named.conf.local'] = { 'content_type': 'mako', 'context': { 'type': node.metadata.get('bind/type'), - 'master_ip': master_ip, - 'views': views, - 'zones': sorted(zones), + 'master_ip': node.metadata.get('bind/master_ip', None), + 'acls': { + **master_node.metadata.get('bind/acls'), + **{ + view_name: view_conf['match_clients'] + for view_name, view_conf in master_node.metadata.get('bind/views').items() + }, + }, + 'views': dict(sorted( + master_node.metadata.get('bind/views').items(), + key=lambda e: (e[1].get('default', False), e[0]), + )), }, 'owner': 'root', 'group': 'bind', @@ -107,26 +92,10 @@ files['/etc/bind/named.conf.local'] = { ], } -def record_matches_view(record, records, view): - if record['type'] in ['A', 'AAAA']: - if view == 'external': - # no internal addresses in external view - if ip_address(record['value']).is_private: - return False - elif view == 'internal': - # external addresses in internal view only, if no internal exists - if ip_address(record['value']).is_global: - for other_record in records: - if ( - record['name'] == other_record['name'] and - record['type'] == other_record['type'] and - ip_address(other_record['value']).is_private - ): - return False - return True - -for view in views: - directories[f"/var/lib/bind/{view['name']}"] = { +for view_name, view_conf in master_node.metadata.get('bind/views').items(): + directories[f"/var/lib/bind/{view_name}"] = { + 'owner': 'bind', + 'group': 'bind', 'purge': True, 'needed_by': [ 'svc_systemd:bind9', @@ -136,29 +105,12 @@ for view in views: ], } - for zone, records in zones.items(): - unique_records = [ - dict(record_tuple) - for record_tuple in set( - tuple(record.items()) for record in records - ) - ] - - files[f"/var/lib/bind/{view['name']}/db.{zone}"] = { + for zone_name, zone_conf in view_conf['zones'].items(): + files[f"/var/lib/bind/{view_name}/db.{zone_name}"] = { + 'owner': 'bind', 'group': 'bind', - 'source': 'db', - 'content_type': 'mako', - 'context': { - 'view': view['name'], - 'serial': datetime.now().strftime('%Y%m%d%H'), - 'records': list(filter( - lambda record: record_matches_view(record, records, view['name']), - unique_records - )), - 'hostname': node.metadata.get('bind/hostname'), - }, 'needs': [ - f"directory:/var/lib/bind/{view['name']}", + f"directory:/var/lib/bind/{view_name}", ], 'needed_by': [ 'svc_systemd:bind9', @@ -167,6 +119,18 @@ for view in views: 'svc_systemd:bind9:restart', ], } + #FIXME: slave doesnt get updated if db doesnt get rewritten on each apply + files[f"/var/lib/bind/{view_name}/db.{zone_name}"].update({ + 'source': 'db', + 'content_type': 'mako', + 'unless': f"test -f /var/lib/bind/{view_name}/db.{zone_name}" if zone_conf.get('allow_update', False) else 'false', + 'context': { + 'serial': datetime.now().strftime('%Y%m%d%H'), + 'records': zone_conf['records'], + 'hostname': node.metadata.get('bind/hostname'), + 'type': node.metadata.get('bind/type'), + }, + }) svc_systemd['bind9'] = {} @@ -175,5 +139,6 @@ actions['named-checkconf'] = { 'unless': 'named-checkconf -z', 'needs': [ 'svc_systemd:bind9', + 'svc_systemd:bind9:restart', ] } diff --git a/bundles/bind/metadata.py b/bundles/bind/metadata.py index bc949b4..7685406 100644 --- a/bundles/bind/metadata.py +++ b/bundles/bind/metadata.py @@ -1,6 +1,7 @@ from ipaddress import ip_interface from json import dumps h = repo.libs.hashable.hashable +repo.libs.bind.repo = repo defaults = { 'apt': { @@ -9,8 +10,36 @@ defaults = { }, }, 'bind': { - 'zones': {}, 'slaves': {}, + 'acls': { + 'our-nets': { + '127.0.0.1', + '10.0.0.0/8', + '169.254.0.0/16', + '172.16.0.0/12', + '192.168.0.0/16', + } + }, + 'views': { + 'internal': { + 'is_internal': True, + 'keys': {}, + 'match_clients': { + 'our-nets', + }, + 'zones': {}, + }, + 'external': { + 'default': True, + 'is_internal': False, + 'keys': {}, + 'match_clients': { + 'any', + }, + 'zones': {}, + }, + }, + 'zones': set(), }, 'telegraf': { 'config': { @@ -28,13 +57,27 @@ defaults = { @metadata_reactor.provides( 'bind/type', + 'bind/master_ip', + 'bind/slave_ips', ) -def type(metadata): - return { - 'bind': { - 'type': 'slave' if metadata.get('bind/master_node', None) else 'master', +def master_slave(metadata): + if metadata.get('bind/master_node', None): + return { + 'bind': { + 'type': 'slave', + 'master_ip': str(ip_interface(repo.get_node(metadata.get('bind/master_node')).metadata.get('network/external/ipv4')).ip), + } + } + else: + return { + 'bind': { + 'type': 'master', + 'slave_ips': { + str(ip_interface(repo.get_node(slave).metadata.get('network/external/ipv4')).ip) + for slave in metadata.get('bind/slaves') + } + } } - } @metadata_reactor.provides( @@ -49,47 +92,52 @@ def dns(metadata): @metadata_reactor.provides( - 'bind/zones', + 'bind/views', ) def collect_records(metadata): if metadata.get('bind/type') == 'slave': return {} - zones = {} - - for other_node in repo.nodes: - for fqdn, records in other_node.metadata.get('dns', {}).items(): - matching_zones = sorted( - filter( - lambda potential_zone: fqdn.endswith(potential_zone), - metadata.get('bind/zones').keys() - ), - key=len, - ) - if matching_zones: - zone = matching_zones[-1] - else: - continue + views = {} - name = fqdn[0:-len(zone) - 1] + for view_name, view_conf in metadata.get('bind/views').items(): + for other_node in repo.nodes: + for fqdn, records in other_node.metadata.get('dns', {}).items(): + matching_zones = sorted( + filter( + lambda potential_zone: fqdn.endswith(potential_zone), + metadata.get('bind/zones') + ), + key=len, + ) + if matching_zones: + zone = matching_zones[-1] + else: + continue - for type, values in records.items(): - for value in values: - zones\ - .setdefault(zone, set())\ - .add( - h({'name': name, 'type': type, 'value': value}) - ) + name = fqdn[0:-len(zone) - 1] + + for type, values in records.items(): + for value in values: + if repo.libs.bind.record_matches_view(value, type, name, zone, view_name, metadata): + views\ + .setdefault(view_name, {})\ + .setdefault('zones', {})\ + .setdefault(zone, {})\ + .setdefault('records', set())\ + .add( + h({'name': name, 'type': type, 'value': value}) + ) return { 'bind': { - 'zones': zones, + 'views': views, }, } @metadata_reactor.provides( - 'bind/zones', + 'bind/views', ) def ns_records(metadata): if metadata.get('bind/type') == 'slave': @@ -104,12 +152,20 @@ def ns_records(metadata): ] return { 'bind': { - 'zones': { - zone: { - # FIXME: bw currently cant handle lists of dicts :( - h({'name': '@', 'type': 'NS', 'value': f"{nameserver}."}) - for nameserver in nameservers - } for zone in metadata.get('bind/zones').keys() + 'views': { + view_name: { + 'zones': { + zone_name: { + 'records': { + # FIXME: bw currently cant handle lists of dicts :( + h({'name': '@', 'type': 'NS', 'value': f"{nameserver}."}) + for nameserver in nameservers + } + } + for zone_name, zone_conf in view_conf['zones'].items() + } + } + for view_name, view_conf in metadata.get('bind/views').items() }, }, } @@ -131,3 +187,65 @@ def slaves(metadata): ], }, } + + +@metadata_reactor.provides( + 'bind/views', +) +def generate_keys(metadata): + if metadata.get('bind/type') == 'slave': + return {} + + return { + 'bind': { + 'views': { + view_name: { + 'keys': { + key: { + 'token':repo.libs.hmac.hmac_sha512( + key, + str(repo.vault.random_bytes_as_base64_for( + f"{metadata.get('id')} bind key {key}", + length=32, + )), + ) + } + for key in view_conf['keys'] + } + } + for view_name, view_conf in metadata.get('bind/views').items() + } + } + } + + +@metadata_reactor.provides( + 'bind/views', +) +def generate_acl_entries_for_keys(metadata): + if metadata.get('bind/type') == 'slave': + return {} + + return { + 'bind': { + 'views': { + view_name: { + 'match_clients': { + # allow keys from this view + *{ + f'key {key}' + for key in view_conf['keys'] + }, + # reject keys from other views + *{ + f'! key {key}' + for other_view_name, other_view_conf in metadata.get('bind/views').items() + if other_view_name != view_name + for key in other_view_conf.get('keys', []) + } + } + } + for view_name, view_conf in metadata.get('bind/views').items() + }, + }, + } diff --git a/bundles/letsencrypt/README.md b/bundles/letsencrypt/README.md new file mode 100644 index 0000000..0214915 --- /dev/null +++ b/bundles/letsencrypt/README.md @@ -0,0 +1,9 @@ +https://github.com/dehydrated-io/dehydrated/wiki/example-dns-01-nsupdate-script + +``` +printf "server 127.0.0.1 +zone acme.resolver.name. +update add _acme-challenge.ckn.li.acme.resolver.name. 600 IN TXT "hello" +send +" | nsupdate -y hmac-sha512:acme:Y9BHl85l352BGZDXa/vg90hh2+5PYe4oJxpkq/oQvIODDkW8bAyQSFr0gKQQxjyIOyYlTjf0MGcdWFv46G/3Rg== +``` diff --git a/bundles/letsencrypt/files/config b/bundles/letsencrypt/files/config index 2d4b2b6..4e53ba6 100644 --- a/bundles/letsencrypt/files/config +++ b/bundles/letsencrypt/files/config @@ -3,3 +3,4 @@ BASEDIR=/var/lib/dehydrated WELLKNOWN="${BASEDIR}/acme-challenges" DOMAINS_TXT="/etc/dehydrated/domains.txt" HOOK="/etc/dehydrated/hook.sh" +CA="https://acme-v02.api.letsencrypt.org/directory" diff --git a/bundles/letsencrypt/files/domains.txt b/bundles/letsencrypt/files/domains.txt index ea7e427..4260aa5 100644 --- a/bundles/letsencrypt/files/domains.txt +++ b/bundles/letsencrypt/files/domains.txt @@ -1,3 +1,3 @@ -% for domain, aliases in sorted(node.metadata.get('letsencrypt/domains', {}).items()): -${domain} ${' '.join(sorted(aliases))} +% for domain, conf in sorted(domains.items()): +${domain} ${' '.join(sorted(conf.get('aliases', [])))} % endfor diff --git a/bundles/letsencrypt/files/hook.sh b/bundles/letsencrypt/files/hook.sh index 4cdf79d..a3d853d 100644 --- a/bundles/letsencrypt/files/hook.sh +++ b/bundles/letsencrypt/files/hook.sh @@ -1,37 +1,57 @@ -deploy_cert() {<%text> - local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" -% for service, config in node.metadata.get('letsencrypt/concat_and_deploy', {}).items(): +set -e +set -u +set -o pipefail - # concat_and_deploy ${service} - if [ "$DOMAIN" = "${config['match_domain']}" ]; then - cat $KEYFILE > ${config['target']} - cat $FULLCHAINFILE >> ${config['target']} -% if 'chown' in config: - chown ${config['chown']} ${config['target']} -% endif -% if 'chmod' in config: - chmod ${config['chmod']} ${config['target']} -% endif -% if 'commands' in config: -% for command in config['commands']: - ${command} -% endfor -% endif - fi -% endfor +deploy_challenge() { + echo " + server 10.0.10.2 + zone ${zone}. + update add $1.${zone}. 60 IN TXT \"$3\" + send + " | tee | nsupdate -y hmac-sha512:${acme_key_name}:${acme_key} + + sleep 10 } - -exit_hook() {<%text> - local ERROR="${1:-}" - -% for service in sorted(node.metadata.get('letsencrypt/reload_after', set())): - systemctl reload-or-restart ${service} -% endfor +clean_challenge() { + echo " + server 10.0.10.2 + zone ${zone}. + update delete $1.${zone}. TXT + send + " | tee | nsupdate -y hmac-sha512:${acme_key_name}:${acme_key} +} + +deploy_cert() { + DOMAIN="$1" + KEYFILE="$2" + CERTFILE="$3" + FULLCHAINFILE="$4" + CHAINFILE="$5" + + case $DOMAIN in + % for domain, conf in sorted(domains.items()): +<% if not conf: continue %>\ + ${domain}) + % if conf.get('location', None): + cat "$KEYFILE" > "${conf['location']}/privkey.pem" + cat "$CERTFILE" > "${conf['location']}/cert.pem" + cat "$FULLCHAINFILE" > "${conf['location']}/fullchain.pem" + cat "$CHAINFILE" > "${conf['location']}/chain.pem" + % endif + % if conf.get('owner', None): + chown ${conf['owner']} "${conf['location']}/privkey.pem" "${conf['location']}/cert.pem" "${conf['location']}/fullchain.pem" "${conf['location']}/chain.pem" + % endif + % for service in sorted(conf.get('reload', [])): + systemctl reload-or-restart ${service} + % endfor + ;; + % endfor + esac } -<%text> HANDLER="$1"; shift -if [[ "${HANDLER}" =~ ^(deploy_cert|exit_hook)$ ]]; then +if [[ $HANDLER =~ ^(deploy_cert|deploy_challenge|clean_challenge)$ ]] +then "$HANDLER" "$@" -fi +fi diff --git a/bundles/letsencrypt/items.py b/bundles/letsencrypt/items.py index 1835866..c92fd68 100644 --- a/bundles/letsencrypt/items.py +++ b/bundles/letsencrypt/items.py @@ -1,6 +1,9 @@ assert node.has_bundle('nginx') +from ipaddress import ip_interface + delegated = 'delegate_to_node' in node.metadata.get('letsencrypt') +acme_node = repo.get_node(node.metadata.get('letsencrypt/acme_node')) directories = { '/etc/dehydrated/conf.d': {}, @@ -10,6 +13,9 @@ directories = { files = { '/etc/dehydrated/domains.txt': { 'content_type': 'mako', + 'context': { + 'domains': node.metadata.get('letsencrypt/domains'), + }, 'triggers': { 'action:letsencrypt_update_certificates', }, @@ -21,6 +27,16 @@ files = { }, '/etc/dehydrated/hook.sh': { 'content_type': 'mako', + 'context': { + 'server': ip_interface(acme_node.metadata.get('network/internal/ipv4')).ip, + 'zone': acme_node.metadata.get('bind/acme_zone'), + 'acme_key_name': 'acme', + 'acme_key': acme_node.metadata.get('bind/views/external/keys/acme/token'), + 'domains': node.metadata.get('letsencrypt/domains'), + }, + 'mode': '0755', + }, + '/etc/dehydrated/letsencrypt-ensure-some-certificate': { 'mode': '0755', }, '/etc/dehydrated/letsencrypt-ensure-some-certificate': { @@ -29,7 +45,7 @@ files = { } actions['letsencrypt_update_certificates'] = { - 'command': 'dehydrated --cron --accept-terms --challenge http-01', + 'command': 'dehydrated --cron --accept-terms --challenge dns-01', 'triggered': True, 'skip': delegated, 'needs': { @@ -48,6 +64,6 @@ for domain in node.metadata.get('letsencrypt/domains').keys(): 'svc_systemd:nginx', }, 'triggers': { - 'action:letsencrypt_update_certificates', + 'action:letsencrypt_update_certificates', }, } diff --git a/bundles/letsencrypt/metadata.py b/bundles/letsencrypt/metadata.py index 631721c..c7caa85 100644 --- a/bundles/letsencrypt/metadata.py +++ b/bundles/letsencrypt/metadata.py @@ -4,71 +4,17 @@ defaults = { 'apt': { 'packages': { 'dehydrated': {}, + 'dnsutils': {}, }, }, 'letsencrypt': { 'domains': { - # 'example.com': {'alias1.example.com', 'alias2.example.com'}, - }, - }, - 'pacman': { - 'packages': { - 'dehydrated': {}, + # 'example.com': { + # 'aliases': {'www.example.com'}, + # 'reload': {'nginx'}, + # 'owner': 'www-data', + # 'location': '/opt/app/certs', + # }, }, }, } - - -@metadata_reactor.provides( - 'systemd-timers/letsencrypt', - 'mirror/certs', -) -def renew(metadata): - delegated_node = metadata.get('letsencrypt/delegate_to_node', False) - - if delegated_node: - delegated_ip = ip_interface(repo.get_node(delegated_node).metadata.get('network/internal/ipv4')).ip - return { - 'mirror': { - 'certs': { - 'from': f"{delegated_ip}:/var/lib/dehydrated/certs", - 'to': '/var/lib/dehydrated', - }, - }, - } - else: - return { - 'systemd-timers': { - 'letsencrypt': { - 'command': '/bin/bash -c "/usr/bin/dehydrated --cron --accept-terms --challenge http-01 && /usr/bin/dehydrated --cleanup"', - 'when': 'daily', - }, - }, - } - - -@metadata_reactor.provides( - 'letsencrypt/domains', - 'dns', -) -def delegated_domains(metadata): - delegated_domains = { - domain - for other_node in repo.nodes - if other_node.has_bundle('letsencrypt') - and other_node.metadata.get('letsencrypt/delegate_to_node', None) == node.name - for domain in other_node.metadata.get('letsencrypt/domains').keys() - } - - return { - 'letsencrypt': { - 'domains': { - domain: set() - for domain in delegated_domains - }, - }, - 'dns': { - domain: repo.libs.dns.get_a_records(metadata, internal=False) - for domain in delegated_domains - }, - } diff --git a/bundles/mailserver/metadata.py b/bundles/mailserver/metadata.py index c853a53..fc885f6 100644 --- a/bundles/mailserver/metadata.py +++ b/bundles/mailserver/metadata.py @@ -60,7 +60,9 @@ def letsencrypt(metadata): return { 'letsencrypt': { 'domains': { - metadata.get('mailserver/hostname'): set(), + metadata.get('mailserver/hostname'): { + 'reload': {'dovecot', 'postfix'}, + }, }, }, } diff --git a/bundles/mosquitto/metadata.py b/bundles/mosquitto/metadata.py index 98b80a2..4ae4c71 100644 --- a/bundles/mosquitto/metadata.py +++ b/bundles/mosquitto/metadata.py @@ -84,7 +84,7 @@ def letsencrypt(metadata): return { 'letsencrypt': { 'domains': { - metadata.get('mosquitto/hostname'): set(), + metadata.get('mosquitto/hostname'): {}, }, }, } diff --git a/bundles/nginx/metadata.py b/bundles/nginx/metadata.py index ad25a3c..8c213b9 100644 --- a/bundles/nginx/metadata.py +++ b/bundles/nginx/metadata.py @@ -104,10 +104,10 @@ def letsencrypt(metadata): return { 'letsencrypt': { 'domains': { - domain: set() for domain in metadata.get('nginx/vhosts').keys() - }, - 'reload_after': { - 'nginx', + domain: { + 'reload': {'nginx'}, + } + for domain in metadata.get('nginx/vhosts').keys() }, }, } diff --git a/groups/all.py b/groups/all.py index 1b8192e..5169847 100644 --- a/groups/all.py +++ b/groups/all.py @@ -17,5 +17,8 @@ }, }, }, + 'letsencrypt': { + 'acme_node': 'htz.mails', + }, } } diff --git a/libs/bind.py b/libs/bind.py new file mode 100644 index 0000000..96b949b --- /dev/null +++ b/libs/bind.py @@ -0,0 +1,29 @@ +from ipaddress import ip_address + +def _values_from_all_nodes(type, name, zone): + return { + value + for node in repo.nodes + for value in node.metadata.get(f'dns/{name}{"." if name else ""}{zone}/{type}', []) + } + +def record_matches_view(value, type, name, zone, view, metadata): + if type not in ['A', 'AAAA']: + return True + + if metadata.get(f'bind/views/{view}/is_internal'): + if ip_address(value).is_private: + return True + elif not list(filter( + lambda other_value: ip_address(other_value).is_private, + _values_from_all_nodes(type, name, zone), + )): + return True + else: + if ip_address(value).is_global: + return True + elif not list(filter( + lambda other_value: ip_address(other_value).is_global, + _values_from_all_nodes(type, name, zone), + )): + return True diff --git a/libs/hmac.py b/libs/hmac.py new file mode 100644 index 0000000..44321c3 --- /dev/null +++ b/libs/hmac.py @@ -0,0 +1,10 @@ +import hmac, hashlib, base64 + +def hmac_sha512(secret, iv): + return base64.b64encode( + hmac.new( + bytes(iv , 'latin-1'), + msg=bytes(secret , 'latin-1'), + digestmod=hashlib.sha512 + ).digest() + ).decode() diff --git a/nodes/home.openhab3.py b/nodes/home.openhab3.py index 7e13842..5d59f32 100644 --- a/nodes/home.openhab3.py +++ b/nodes/home.openhab3.py @@ -30,9 +30,6 @@ 'gateway4': '10.0.0.1', }, }, - 'letsencrypt': { - 'delegate_to_node': 'htz.mails', - }, 'nginx': { 'vhosts': { 'openhab.ckn.li': { @@ -43,6 +40,11 @@ }, }, }, + 'letsencrypt': { + 'domains': { + 'test12.ckn.li': {}, + } + }, 'java': { 'version': 11, }, diff --git a/nodes/home.server.py b/nodes/home.server.py index 4d57ce1..71ad55d 100644 --- a/nodes/home.server.py +++ b/nodes/home.server.py @@ -50,9 +50,6 @@ 'readonly_token': '!decrypt:encrypt$gAAAAABg3z1-0hnUdzsfivocxhJm58YnPLn96OUvnHiPaehdRhKd6TZBgEPc5YyR07t2-GEUfOvEwoie-O6QsVhWYxrwxNTBXux_iUSx7W6e-fLQA_3MgWf5G97q_3kx_wCgQ6V0iKRyxH988TpNSMACfS4WhCXdSes1CaMpic4VV3S3ox_gCrSHxO7yVXQkJDnOW0MixY5T', 'writeonly_token': '!decrypt:encrypt$gAAAAABg3z6fGrOy2tNdo03RoYAXmpJoJYkfhBfpblPh_wxYfqmdjtABaD7XyV9mSh9xl8oWQlTAtCk9KndVCDQy7BJ-ju7S3HCKJ0k244Y5YKxUnQtqt9fc9nnm8XD-NOJqLKyfy0QhL_I8dFT02pygoJeCUR5NkZcTKf6julb-iGXI6vWcQgolJTYrW643pHObd-Z-vIEl', }, - 'letsencrypt': { - 'delegate_to_node': 'htz.mails', - }, 'mosquitto': { 'hostname': 'mqtt.sublimity.de', 'users': { diff --git a/nodes/htz.mails.py b/nodes/htz.mails.py index b14b07b..cb825e8 100644 --- a/nodes/htz.mails.py +++ b/nodes/htz.mails.py @@ -10,6 +10,7 @@ 'dnsserver', ], 'bundles': [ + 'bind-acme', 'islamicstate.eu', 'wireguard', 'zfs', @@ -31,18 +32,19 @@ }, 'bind': { 'hostname': 'resolver.name', + 'acme_zone': 'acme.sublimity.de', 'zones': { - 'sublimity.de': {}, - 'freibrief.net': {}, - 'nadenau.net': {}, - 'naeder.net': {}, - 'rolfwerner.eu': {}, - 'wettengl.net': {}, - 'wingl.de': {}, - 'woodpipe.de': {}, - 'ckn.li': {}, - 'islamicstate.eu': {}, - 'hausamsilberberg.de': {}, + 'sublimity.de', + 'freibrief.net', + 'nadenau.net', + 'naeder.net', + 'rolfwerner.eu', + 'wettengl.net', + 'wingl.de', + 'woodpipe.de', + 'ckn.li', + 'islamicstate.eu', + 'hausamsilberberg.de', }, }, 'dns': { @@ -61,9 +63,9 @@ }, 'letsencrypt': { 'domains': { - 'ckn.li': set(), - 'sublimity.de': set(), - 'freibrief.net': set(), + 'ckn.li': {}, + 'sublimity.de': {}, + 'freibrief.net': {}, }, }, 'mailserver': {