gnu: Add knot-service-type.

* gnu/services/dns.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* doc/guix.texi (DNS Services): New subsubsection.
This commit is contained in:
Julien Lepiller 2017-05-01 21:41:45 +02:00
parent d771ba62f8
commit ba69e8f7ce
No known key found for this signature in database
GPG key ID: 43111F4520086A0C
3 changed files with 1004 additions and 0 deletions

View file

@ -218,6 +218,7 @@ Services
* Messaging Services:: Messaging services.
* Kerberos Services:: Kerberos services.
* Web Services:: Web servers.
* DNS Services:: DNS daemons.
* VPN Services:: VPN daemons.
* Network File System:: NFS related services.
* Continuous Integration:: The Cuirass service.
@ -8737,6 +8738,7 @@ declaration.
* Messaging Services:: Messaging services.
* Kerberos Services:: Kerberos services.
* Web Services:: Web servers.
* DNS Services:: DNS daemons.
* VPN Services:: VPN daemons.
* Network File System:: NFS related services.
* Continuous Integration:: The Cuirass service.
@ -13520,6 +13522,414 @@ Whether the server should add its configuration to response.
@end table
@end deftp
@node DNS Services
@subsubsection DNS Services
@cindex DNS (domain name system)
@cindex domain name system (DNS)
The @code{(gnu services dns)} module provides services related to the
@dfn{domain name system} (DNS). It provides a server service for hosting
an @emph{authoritative} DNS server for multiple zones, slave or master.
This service uses @uref{https://www.knot-dns.cz/, Knot DNS}.
An example configuration of an authoritative server for two zones, one master
and one slave, is:
@lisp
(define-zone-entries example.org.zone
;; Name TTL Class Type Data
("@@" "" "IN" "A" "127.0.0.1")
("@@" "" "IN" "NS" "ns")
("ns" "" "IN" "A" "127.0.0.1"))
(define master-zone
(knot-zone-configuration
(domain "example.org")
(zone (zone-file
(origin "example.org")
(entries example.org.zone)))))
(define slave-zone
(knot-zone-configuration
(domain "plop.org")
(dnssec-policy "default")
(master (list "plop-master"))))
(define plop-master
(knot-remote-configuration
(id "plop-master")
(address (list "208.76.58.171"))))
(operating-system
;; ...
(services (cons* (service knot-service-type
(knot-confifguration
(remotes (list plop-master))
(zones (list master-zone slave-zone))))
;; ...
%base-services)))
@end lisp
@deffn {Scheme Variable} knot-service-type
This is the type for the Knot DNS server.
Knot DNS is an authoritative DNS server, meaning that it can serve multiple
zones, that is to say domain names you would buy from a registrar. This server
is not a resolver, meaning that it can only resolve names for which it is
authoritative. This server can be configured to serve zones as a master server
or a slave server as a per-zone basis. Slave zones will get their data from
masters, and will serve it as an authoritative server. From the point of view
of a resolver, there is no difference between master and slave.
The following data types are used to configure the Knot DNS server:
@end deffn
@deftp {Data Type} knot-key-configuration
Data type representing a key.
This type has the following parameters:
@table @asis
@item @code{id} (default: @code{""})
An identifier for other configuration fields to refer to this key. IDs must
be unique and must not be empty.
@item @code{algorithm} (default: @code{#f})
The algorithm to use. Choose between @code{#f}, @code{'hmac-md5},
@code{'hmac-sha1}, @code{'hmac-sha224}, @code{'hmac-sha256}, @code{'hmac-sha384}
and @code{'hmac-sha512}.
@item @code{secret} (default: @code{""})
The secret key itself.
@end table
@end deftp
@deftp {Data Type} knot-acl-configuration
Data type representing an Access Control List (ACL) configuration.
This type has the following parameters:
@table @asis
@item @code{id} (default: @code{""})
An identifier for ether configuration fields to refer to this key. IDs must be
unique and must not be empty.
@item @code{address} (default: @code{'()})
An ordered list of IP addresses, network subnets, or network ranges represented
with strings. The query must match one of them. Empty value means that
address match is not required.
@item @code{key} (default: @code{'()})
An ordered list of references to keys represented with strings. The string
must match a key ID defined in a @code{knot-key-configuration}. No key means
that a key is not require to match that ACL.
@item @code{action} (default: @code{'()})
An ordered list of actions that are permitted or forbidden by this ACL. Possible
values are lists of zero or more elements from @code{'transfer}, @code{'notify}
and @code{'update}.
@item @code{deny?} (default: @code{#f})
When true, the ACL defines restrictions. Listed actions are forbidden. When
false, listed actions are allowed.
@end table
@end deftp
@deftp {Data Type} zone-entry
Data type represnting a record entry in a zone file.
This type has the following parameters:
@table @asis
@item @code{name} (default: @code{"@@"})
The name of the record. @code{"@@"} refers to the origin of the zone. Names
are relative to the origin of the zone. For example, in the @code{example.org}
zone, @code{"ns.example.org"} actually refers to @code{ns.example.org.example.org}.
Names ending with a dot are absolute, which means that @code{"ns.example.org."}
refers to @code{ns.example.org}.
@item @code{ttl} (default: @code{""})
The Time-To-Live (TTL) of this record. If not set, the default TTL is used.
@item @code{class} (default: @code{"IN"})
The class of the record. Knot currently supports only @code{"IN"} and
partially @code{"CH"}.
@item @code{type} (default: @code{"A"})
The type of the record. Common types include A (IPv4 address), AAAA (IPv6
address), NS (Name Server) and MX (Mail eXchange). Many other types are
defined.
@item @code{data} (default: @code{""})
The data contained in the record. For instance an IP address associated with
an A record, or a domain name associated with an NS record. Remember that
domain names are relative to the origin unless they end with a dot.
@end table
@end deftp
@deftp {Data Type} zone-file
Data type representing the content of a zone file.
This type has the following parameters:
@table @asis
@item @code{entries} (default: @code{'()})
The list of entries. The SOA record is taken care of, so you don't need to
put it in the list of entries. This list should probably contain an entry
for your primary authoritative DNS server. Other than using a list of entries
directly, you can use @code{define-zone-entries} to define a object containing
the list of entries more easily, that you can later pass to the @code{entries}
field of the @code{zone-file}.
@item @code{origin} (default: @code{""})
The name of your zone. This parameter cannot be empty.
@item @code{ns} (default: @code{"ns"})
The domain of your primary authoritative DNS server. The name is relative to
the origin, unless it ends with a dot. It is mandatory that this primary
DNS server corresponds to an NS record in the zone and that it is associated
to an IP address in the list of entries.
@item @code{mail} (default: @code{"hostmaster"})
An email address people can contact you at, as the owner of the zone. This
is translated as @code{<mail>@@<origin>}.
@item @code{serial} (default: @code{1})
The serial number of the zone. As this is used to keep track of changes by
both slaves and resolvers, it is mandatory that it @emph{never} decreases.
Always increment it when you make a change in your zone.
@item @code{refresh} (default: @code{"2d"})
The frequency at which slaves will do a zone transfer. This value can be
a number of seconds or a number of some unit between:
@itemize
@item m: minute
@item h: hour
@item d: day
@item w: week
@end itemize
@item @code{retry} (default: @code{"15m"})
The period after which a slave will retry to contact its master when it fails
to do so a first time.
@item @code{expiry} (default: @code{"2w"})
Default TTL of records. Existing records are considered correct for at most
this amount of time. After this period, resolvers will invalidate their cache
and check again that it still exists.
@item @code{nx} (default: @code{"1h"})
Default TTL of inexistant records. This delay is usually short because you want
your new domains to reach everyone quickly.
@end table
@end deftp
@deftp {Data Type} knot-remote-configuration
Data type representing a remote configuration.
This type has the following parameters:
@table @asis
@item @code{id} (default: @code{""})
An identifier for other configuration fields to refer to this remote. IDs must
be unique and must not be empty.
@item @code{address} (default: @code{'()})
An ordered list of destination IP addresses. Addresses are tried in sequence.
An optional port can be given with the @@ separator. For instance:
@code{(list "1.2.3.4" "2.3.4.5@@53")}. Default port is 53.
@item @code{via} (default: @code{'()})
An ordered list of source IP addresses. An empty list will have Knot choose
an appropriate source IP. An optional port can be given with the @@ separator.
The default is to choose at random.
@item @code{key} (default: @code{#f})
A reference to a key, that is a string containing the identifier of a key
defined in a @code{knot-key-configuration} field.
@end table
@end deftp
@deftp {Data Type} knot-keystore-configuration
Data type representing a keystore to hold dnssec keys.
This type has the following parameters:
@table @asis
@item @code{id} (default: @code{""})
The id of the keystore. It must not be empty.
@item @code{backend} (default: @code{'pem})
The backend to store the keys in. Can be @code{'pem} or @code{'pkcs11}.
@item @code{config} (default: @code{"/var/lib/knot/keys/keys"})
The configuration string of the backend. An example for the PKCS#11 is:
@code{"pkcs11:token=knot;pin-value=1234 /gnu/store/.../lib/pkcs11/libsofthsm2.so"}.
For the pem backend, the string reprensents a path in the filesystem.
@end table
@end deftp
@deftp {Data Type} knot-policy-configuration
Data type representing a dnssec policy. Knot DNS is able to automatically
sign your zones. It can either generate and manage your keys automatically or
use keys that you generate.
Dnssec is usually implemented using two keys: a Key Signing Key (KSK) that is
used to sign the second, and a Zone Signing Key (ZSK) that is used to sign the
zone. In order to be trusted, the KSK needs to be present in the parent zone
(usually a top-level domain). If your registrar supports dnssec, you will
have to send them your KSK's hash so they can add a DS record in their zone.
This is not automated and need to be done each time you change your KSK.
The policy also defines the lifetime of keys. Usually, ZSK can be changed
easily and use weaker cryptographic functions (they use lower parameters) in
order to sign records quickly, so they are changed often. The KSK however
requires manual interaction with the registrar, so they are changed less often
and use stronger parameters because they sign only one record.
This type has the following parameters:
@table @asis
@item @code{id} (default: @code{""})
The id of the policy. It must not be empty.
@item @code{keystore} (default: @code{"default"})
A reference to a keystore, that is a string containing the identifier of a
keystore defined in a @code{knot-keystore-configuration} field. The
@code{"default"} identifier means the default keystore (a kasp database that
was setup by this service).
@item @code{manual?} (default: @code{#f})
Whether the key management is manual or automatic.
@item @code{single-type-signing?} (default: @code{#f})
When @code{#t}, use the Single-Type Signing Scheme.
@item @code{algorithm} (default: @code{"ecdsap256sha256"})
An algorithm of signing keys and issued signatures.
@item @code{ksk-size} (default: @code{256})
The length of the KSK. Note that this value is correct for the default
algorithm, but would be unsecure for other algorithms.
@item @code{zsk-size} (default: @code{256})
The length of the ZSK. Note that this value is correct for the default
algorithm, but would be unsecure for other algorithms.
@item @code{dnskey-ttl} (default: @code{'default})
The TTL value for DNSKEY records added into zone apex. The special
@code{'default} value means same as the zone SOA TTL.
@item @code{zsk-lifetime} (default: @code{"30d"})
The period between ZSK publication and the next rollover initiation.
@item @code{propagation-delay} (default: @code{"1d"})
An extra delay added for each key rollover step. This value should be high
enough to cover propagation of data from the master server to all slaves.
@item @code{rrsig-lifetime} (default: @code{"14d"})
A validity period of newly issued signatures.
@item @code{rrsig-refresh} (default: @code{"7d"})
A period how long before a signature expiration the signature will be refreshed.
@item @code{nsec3?} (default: @code{#f})
When @code{#t}, NSEC3 will be used instead of NSEC.
@item @code{nsec3-iterations} (default: @code{5})
The number of additional times the hashing is performed.
@item @code{nsec3-salt-length} (default: @code{8})
The length of a salt field in octets, which is appended to the original owner
name before hashing.
@item @code{nsec3-salt-lifetime} (default: @code{"30d"})
The validity period of newly issued salt field.
@end table
@end deftp
@deftp {Data Type} knot-zone-configuration
Data type representing a zone served by Knot.
This type has the following parameters:
@table @asis
@item @code{domain} (default: @code{""})
The domain served by this configuration. It must not be empty.
@item @code{file} (default: @code{""})
The file where this zone is saved. This parameter is ignored by master zones.
Empty means default location that depends on the domain name.
@item @code{zone} (default: @code{(zone-file)})
The content of the zone file. This parameter is ignored by slave zones. It
must contain a zone-file record.
@item @code{master} (default: @code{'()})
A list of master remotes. When empty, this zone is a master. When set, this
zone is a slave. This is a list of remotes identifiers.
@item @code{ddns-master} (default: @code{#f})
The main master. When empty, it defaults to the first master in the list of
masters.
@item @code{notify} (default: @code{'()})
A list of slave remote identifiers.
@item @code{acl} (default: @code{'()})
A list of acl identifiers.
@item @code{semantic-checks?} (default: @code{#f})
When set, this adds more semantic checks to the zone.
@item @code{disable-any?} (default: @code{#f})
When set, this forbids queries of the ANY type.
@item @code{zonefile-sync} (default: @code{0})
The delay between a modification in memory and on disk. 0 means immediate
synchronization.
@item @code{serial-policy} (default: @code{'increment})
A policy between @code{'increment} and @code{'unixtime}.
@end table
@end deftp
@deftp {Data Type} knot-configuration
Data type representing the Knot configuration.
This type has the following parameters:
@table @asis
@item @code{knot} (default: @code{knot})
The Knot package.
@item @code{run-directory} (default: @code{"/var/run/knot"})
The run directory. This directory will be used for pid file and sockets.
@item @code{listen-v4} (default: @code{"0.0.0.0"})
An ip address on which to listen.
@item @code{listen-v6} (default: @code{"::"})
An ip address on which to listen.
@item @code{listen-port} (default: @code{53})
A port on which to listen.
@item @code{keys} (default: @code{'()})
The list of knot-key-configuration used by this configuration.
@item @code{acls} (default: @code{'()})
The list of knot-acl-configuration used by this configuration.
@item @code{remotes} (default: @code{'()})
The list of knot-remote-configuration used by this configuration.
@item @code{zones} (default: @code{'()})
The list of knot-zone-configuration used by this configuration.
@end table
@end deftp
@node VPN Services
@subsubsection VPN Services
@cindex VPN (virtual private network)

View file

@ -426,6 +426,7 @@ GNU_SYSTEM_MODULES = \
%D%/services/dbus.scm \
%D%/services/desktop.scm \
%D%/services/dict.scm \
%D%/services/dns.scm \
%D%/services/kerberos.scm \
%D%/services/lirc.scm \
%D%/services/mail.scm \

593
gnu/services/dns.scm Normal file
View file

@ -0,0 +1,593 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
(define-module (gnu services dns)
#:use-module (gnu services)
#:use-module (gnu services configuration)
#:use-module (gnu services shepherd)
#:use-module (gnu system shadow)
#:use-module (gnu packages admin)
#:use-module (gnu packages dns)
#:use-module (guix packages)
#:use-module (guix records)
#:use-module (guix gexp)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
#:export (knot-service-type
knot-acl-configuration
knot-key-configuration
knot-keystore-configuration
knot-zone-configuration
knot-remote-configuration
knot-policy-configuration
knot-configuration
define-zone-entries
zone-file
zone-entry))
;;;
;;; Knot DNS.
;;;
(define-record-type* <knot-key-configuration>
knot-key-configuration make-knot-key-configuration
knot-key-configuration?
(id knot-key-configuration-id
(default ""))
(algorithm knot-key-configuration-algorithm
(default #f)); one of #f, or an algorithm name
(secret knot-key-configuration-secret
(default "")))
(define-record-type* <knot-acl-configuration>
knot-acl-configuration make-knot-acl-configuration
knot-acl-configuration?
(id knot-acl-configuration-id
(default ""))
(address knot-acl-configuration-address
(default '()))
(key knot-acl-configuration-key
(default '()))
(action knot-acl-configuration-action
(default '()))
(deny? knot-acl-configuration-deny?
(default #f)))
(define-record-type* <zone-entry>
zone-entry make-zone-entry
zone-entry?
(name zone-entry-name
(default "@"))
(ttl zone-entry-ttl
(default ""))
(class zone-entry-class
(default "IN"))
(type zone-entry-type
(default "A"))
(data zone-entry-data
(default "")))
(define-record-type* <zone-file>
zone-file make-zone-file
zone-file?
(entries zone-file-entries
(default '()))
(origin zone-file-origin
(default ""))
(ns zone-file-ns
(default "ns"))
(mail zone-file-mail
(default "hostmaster"))
(serial zone-file-serial
(default 1))
(refresh zone-file-refresh
(default "2d"))
(retry zone-file-retry
(default "15m"))
(expiry zone-file-expiry
(default "2w"))
(nx zone-file-nx
(default "1h")))
(define-record-type* <knot-keystore-configuration>
knot-keystore-configuration make-knot-keystore-configuration
knot-keystore-configuration?
(id knot-keystore-configuration-id
(default ""))
(backend knot-keystore-configuration-backend
(default 'pem))
(config knot-keystore-configuration-config
(default "/var/lib/knot/keys/keys")))
(define-record-type* <knot-policy-configuration>
knot-policy-configuration make-knot-policy-configuration
knot-policy-configuration?
(id knot-policy-configuration-id
(default ""))
(keystore knot-policy-configuration-keystore
(default "default"))
(manual? knot-policy-configuration-manual?
(default #f))
(single-type-signing? knot-policy-configuration-single-type-signing?
(default #f))
(algorithm knot-policy-configuration-algorithm
(default "ecdsap256sha256"))
(ksk-size knot-policy-configuration-ksk-size
(default 256))
(zsk-size knot-policy-configuration-zsk-size
(default 256))
(dnskey-ttl knot-policy-configuration-dnskey-ttl
(default 'default))
(zsk-lifetime knot-policy-configuration-zsk-lifetime
(default "30d"))
(propagation-delay knot-policy-configuration-propagation-delay
(default "1d"))
(rrsig-lifetime knot-policy-configuration-rrsig-lifetime
(default "14d"))
(rrsig-refresh knot-policy-configuration-rrsig-refresh
(default "7d"))
(nsec3? knot-policy-configuration-nsec3?
(default #f))
(nsec3-iterations knot-policy-configuration-nsec3-iterations
(default 5))
(nsec3-salt-length knot-policy-configuration-nsec3-salt-length
(default 8))
(nsec3-salt-lifetime knot-policy-configuration-nsec3-salt-lifetime
(default "30d")))
(define-record-type* <knot-zone-configuration>
knot-zone-configuration make-knot-zone-configuration
knot-zone-configuration?
(domain knot-zone-configuration-domain
(default ""))
(file knot-zone-configuration-file
(default "")) ; the file where this zone is saved.
(zone knot-zone-configuration-zone
(default (zone-file))) ; initial content of the zone file
(master knot-zone-configuration-master
(default '()))
(ddns-master knot-zone-configuration-ddns-master
(default #f))
(notify knot-zone-configuration-notify
(default '()))
(acl knot-zone-configuration-acl
(default '()))
(semantic-checks? knot-zone-configuration-semantic-checks?
(default #f))
(disable-any? knot-zone-configuration-disable-any?
(default #f))
(zonefile-sync knot-zone-configuration-zonefile-sync
(default 0))
(dnssec-policy knot-zone-configuration-dnssec-policy
(default #f))
(serial-policy knot-zone-configuration-serial-policy
(default 'increment)))
(define-record-type* <knot-remote-configuration>
knot-remote-configuration make-knot-remote-configuration
knot-remote-configuration?
(id knot-remote-configuration-id
(default ""))
(address knot-remote-configuration-address
(default '()))
(via knot-remote-configuration-via
(default '()))
(key knot-remote-configuration-key
(default #f)))
(define-record-type* <knot-configuration>
knot-configuration make-knot-configuration
knot-configuration?
(knot knot-configuration-knot
(default knot))
(run-directory knot-configuration-run-directory
(default "/var/run/knot"))
(listen-v4 knot-configuration-listen-v4
(default "0.0.0.0"))
(listen-v6 knot-configuration-listen-v6
(default "::"))
(listen-port knot-configuration-listen-port
(default 53))
(keys knot-configuration-keys
(default '()))
(keystores knot-configuration-keystores
(default '()))
(acls knot-configuration-acls
(default '()))
(remotes knot-configuration-remotes
(default '()))
(policies knot-configuration-policies
(default '()))
(zones knot-configuration-zones
(default '())))
(define-syntax define-zone-entries
(syntax-rules ()
((_ id (name ttl class type data) ...)
(define id (list (make-zone-entry name ttl class type data) ...)))))
(define (error-out msg)
(raise (condition (&message (message msg)))))
(define (verify-knot-key-configuration key)
(unless (knot-key-configuration? key)
(error-out "keys must be a list of only knot-key-configuration."))
(let ((id (knot-key-configuration-id key)))
(unless (and (string? id) (not (equal? id "")))
(error-out "key id must be a non empty string.")))
(unless (memq '(#f hmac-md5 hmac-sha1 hmac-sha224 hmac-sha256 hmac-sha384 hmac-sha512)
(knot-key-configuration-algorithm key))
(error-out "algorithm must be one of: #f, 'hmac-md5, 'hmac-sha1,
'hmac-sha224, 'hmac-sha256, 'hmac-sha384 or 'hmac-sha512")))
(define (verify-knot-keystore-configuration keystore)
(unless (knot-keystore-configuration? keystore)
(error-out "keystores must be a list of only knot-keystore-configuration."))
(let ((id (knot-keystore-configuration-id keystore)))
(unless (and (string? id) (not (equal? id "")))
(error-out "keystore id must be a non empty string.")))
(unless (memq '(pem pkcs11)
(knot-keystore-configuration-backend keystore))
(error-out "backend must be one of: 'pem or 'pkcs11")))
(define (verify-knot-policy-configuration policy)
(unless (knot-keystore-configuration? policy)
(error-out "policies must be a list of only knot-policy-configuration."))
(let ((id (knot-policy-configuration-id policy)))
(unless (and (string? id) (not (equal? id "")))
(error-out "policy id must be a non empty string."))))
(define (verify-knot-acl-configuration acl)
(unless (knot-acl-configuration? acl)
(error-out "acls must be a list of only knot-acl-configuration."))
(let ((id (knot-acl-configuration-id acl))
(address (knot-acl-configuration-address acl))
(key (knot-acl-configuration-key acl))
(action (knot-acl-configuration-action acl)))
(unless (and (string? id) (not (equal? id "")))
(error-out "acl id must be a non empty string."))
(unless (and (list? address)
(fold (lambda (x1 x2) (and (string? x1) (string? x2))) "" address))
(error-out "acl address must be a list of strings.")))
(unless (boolean? (knot-acl-configuration-deny? acl))
(error-out "deny? must be #t or #f.")))
(define (verify-knot-zone-configuration zone)
(unless (knot-zone-configuration? zone)
(error-out "zones must be a list of only knot-zone-configuration."))
(let ((domain (knot-zone-configuration-domain zone)))
(unless (and (string? domain) (not (equal? domain "")))
(error-out "zone domain must be a non empty string."))))
(define (verify-knot-remote-configuration remote)
(unless (knot-remote-configuration? remote)
(error-out "remotes must be a list of only knot-remote-configuration."))
(let ((id (knot-remote-configuration-id remote)))
(unless (and (string? id) (not (equal? id "")))
(error-out "remote id must be a non empty string."))))
(define (verify-knot-configuration config)
(unless (package? (knot-configuration-knot config))
(error-out "knot configuration field must be a package."))
(unless (string? (knot-configuration-run-directory config))
(error-out "run-directory must be a string."))
(unless (list? (knot-configuration-keys config))
(error-out "keys must be a list of knot-key-configuration."))
(for-each (lambda (key) (verify-knot-key-configuration key))
(knot-configuration-keys config))
(unless (list? (knot-configuration-keystores config))
(error-out "keystores must be a list of knot-keystore-configuration."))
(for-each (lambda (keystore) (verify-knot-keystore-configuration keystore))
(knot-configuration-keystores config))
(unless (list? (knot-configuration-acls config))
(error-out "acls must be a list of knot-acl-configuration."))
(for-each (lambda (acl) (verify-knot-acl-configuration acl))
(knot-configuration-acls config))
(unless (list? (knot-configuration-zones config))
(error-out "zones must be a list of knot-zone-configuration."))
(for-each (lambda (zone) (verify-knot-zone-configuration zone))
(knot-configuration-zones config))
(unless (list? (knot-configuration-policies config))
(error-out "policies must be a list of knot-policy-configuration."))
(for-each (lambda (policy) (verify-knot-policy-configuration policy))
(knot-configuration-policies config))
(unless (list? (knot-configuration-remotes config))
(error-out "remotes must be a list of knot-remote-configuration."))
(for-each (lambda (remote) (verify-knot-remote-configuration remote))
(knot-configuration-remotes config))
#t)
(define (format-string-list l)
"Formats a list of string in YAML"
(if (eq? l '())
""
(let ((l (reverse l)))
(string-append
"["
(fold (lambda (x1 x2)
(string-append (if (symbol? x1) (symbol->string x1) x1) ", "
(if (symbol? x2) (symbol->string x2) x2)))
(car l) (cdr l))
"]"))))
(define (knot-acl-config acls)
(with-output-to-string
(lambda ()
(for-each
(lambda (acl-config)
(let ((id (knot-acl-configuration-id acl-config))
(address (knot-acl-configuration-address acl-config))
(key (knot-acl-configuration-key acl-config))
(action (knot-acl-configuration-action acl-config))
(deny? (knot-acl-configuration-deny? acl-config)))
(format #t " - id: ~a\n" id)
(unless (eq? address '())
(format #t " address: ~a\n" (format-string-list address)))
(unless (eq? key '())
(format #t " key: ~a\n" (format-string-list key)))
(unless (eq? action '())
(format #t " action: ~a\n" (format-string-list action)))
(format #t " deny: ~a\n" (if deny? "on" "off"))))
acls))))
(define (knot-key-config keys)
(with-output-to-string
(lambda ()
(for-each
(lambda (key-config)
(let ((id (knot-key-configuration-id key-config))
(algorithm (knot-key-configuration-algorithm key-config))
(secret (knot-key-configuration-secret key-config)))
(format #t " - id: ~a\n" id)
(if algorithm
(format #t " algorithm: ~a\n" (symbol->string algorithm)))
(format #t " secret: ~a\n" secret)))
keys))))
(define (knot-keystore-config keystores)
(with-output-to-string
(lambda ()
(for-each
(lambda (keystore-config)
(let ((id (knot-keystore-configuration-id keystore-config))
(backend (knot-keystore-configuration-backend keystore-config))
(config (knot-keystore-configuration-config keystore-config)))
(format #t " - id: ~a\n" id)
(format #t " backend: ~a\n" (symbol->string backend))
(format #t " config: \"~a\"\n" config)))
keystores))))
(define (knot-policy-config policies)
(with-output-to-string
(lambda ()
(for-each
(lambda (policy-config)
(let ((id (knot-policy-configuration-id policy-config))
(keystore (knot-policy-configuration-keystore policy-config))
(manual? (knot-policy-configuration-manual? policy-config))
(single-type-signing? (knot-policy-configuration-single-type-signing?
policy-config))
(algorithm (knot-policy-configuration-algorithm policy-config))
(ksk-size (knot-policy-configuration-ksk-size policy-config))
(zsk-size (knot-policy-configuration-zsk-size policy-config))
(dnskey-ttl (knot-policy-configuration-dnskey-ttl policy-config))
(zsk-lifetime (knot-policy-configuration-zsk-lifetime policy-config))
(propagation-delay (knot-policy-configuration-propagation-delay
policy-config))
(rrsig-lifetime (knot-policy-configuration-rrsig-lifetime
policy-config))
(nsec3? (knot-policy-configuration-nsec3? policy-config))
(nsec3-iterations (knot-policy-configuration-nsec3-iterations
policy-config))
(nsec3-salt-length (knot-policy-configuration-nsec3-salt-length
policy-config))
(nsec3-salt-lifetime (knot-policy-configuration-nsec3-salt-lifetime
policy-config)))
(format #t " - id: ~a\n" id)
(format #t " keystore: ~a\n" keystore)
(format #t " manual: ~a\n" (if manual? "on" "off"))
(format #t " single-type-signing: ~a\n" (if single-type-signing?
"on" "off"))
(format #t " algorithm: ~a\n" algorithm)
(format #t " ksk-size: ~a\n" (number->string ksk-size))
(format #t " zsk-size: ~a\n" (number->string zsk-size))
(unless (eq? dnskey-ttl 'default)
(format #t " dnskey-ttl: ~a\n" dnskey-ttl))
(format #t " zsk-lifetime: ~a\n" zsk-lifetime)
(format #t " propagation-delay: ~a\n" propagation-delay)
(format #t " rrsig-lifetime: ~a\n" rrsig-lifetime)
(format #t " nsec3: ~a\n" (if nsec3? "on" "off"))
(format #t " nsec3-iterations: ~a\n"
(number->string nsec3-iterations))
(format #t " nsec3-salt-length: ~a\n"
(number->string nsec3-salt-length))
(format #t " nsec3-salt-lifetime: ~a\n" nsec3-salt-lifetime)))
policies))))
(define (knot-remote-config remotes)
(with-output-to-string
(lambda ()
(for-each
(lambda (remote-config)
(let ((id (knot-remote-configuration-id remote-config))
(address (knot-remote-configuration-address remote-config))
(via (knot-remote-configuration-via remote-config))
(key (knot-remote-configuration-key remote-config)))
(format #t " - id: ~a\n" id)
(unless (eq? address '())
(format #t " address: ~a\n" (format-string-list address)))
(unless (eq? via '())
(format #t " via: ~a\n" (format-string-list via)))
(if key
(format #t " key: ~a\n" key))))
remotes))))
(define (serialize-zone-entries entries)
(with-output-to-string
(lambda ()
(for-each
(lambda (entry)
(let ((name (zone-entry-name entry))
(ttl (zone-entry-ttl entry))
(class (zone-entry-class entry))
(type (zone-entry-type entry))
(data (zone-entry-data entry)))
(format #t "~a ~a ~a ~a ~a\n" name ttl class type data)))
entries))))
(define (serialize-zone-file zone domain)
(computed-file (string-append domain ".zone")
#~(begin
(call-with-output-file #$output
(lambda (port)
(format port "$ORIGIN ~a.\n"
#$(zone-file-origin zone))
(format port "@ IN SOA ~a ~a (~a ~a ~a ~a ~a)\n"
#$(zone-file-ns zone)
#$(zone-file-mail zone)
#$(zone-file-serial zone)
#$(zone-file-refresh zone)
#$(zone-file-retry zone)
#$(zone-file-expiry zone)
#$(zone-file-nx zone))
(format port "~a\n"
#$(serialize-zone-entries (zone-file-entries zone))))))))
(define (knot-zone-config zone)
(let ((content (knot-zone-configuration-zone zone)))
#~(with-output-to-string
(lambda ()
(let ((domain #$(knot-zone-configuration-domain zone))
(file #$(knot-zone-configuration-file zone))
(master (list #$@(knot-zone-configuration-master zone)))
(ddns-master #$(knot-zone-configuration-ddns-master zone))
(notify (list #$@(knot-zone-configuration-notify zone)))
(acl (list #$@(knot-zone-configuration-acl zone)))
(semantic-checks? #$(knot-zone-configuration-semantic-checks? zone))
(disable-any? #$(knot-zone-configuration-disable-any? zone))
(dnssec-policy #$(knot-zone-configuration-dnssec-policy zone))
(serial-policy '#$(knot-zone-configuration-serial-policy zone)))
(format #t " - domain: ~a\n" domain)
(if (eq? master '())
;; This server is a master
(if (equal? file "")
(format #t " file: ~a\n"
#$(serialize-zone-file content
(knot-zone-configuration-domain zone)))
(format #t " file: ~a\n" file))
;; This server is a slave (has masters)
(begin
(format #t " master: ~a\n"
#$(format-string-list
(knot-zone-configuration-master zone)))
(if ddns-master (format #t " ddns-master ~a\n" ddns-master))))
(unless (eq? notify '())
(format #t " notify: ~a\n"
#$(format-string-list
(knot-zone-configuration-notify zone))))
(unless (eq? acl '())
(format #t " acl: ~a\n"
#$(format-string-list
(knot-zone-configuration-acl zone))))
(format #t " semantic-checks: ~a\n" (if semantic-checks? "on" "off"))
(format #t " disable-any: ~a\n" (if disable-any? "on" "off"))
(if dnssec-policy
(begin
(format #t " dnssec-signing: on\n")
(format #t " dnssec-policy: ~a\n" dnssec-policy)))
(format #t " serial-policy: ~a\n"
(symbol->string serial-policy)))))))
(define (knot-config-file config)
(verify-knot-configuration config)
(computed-file "knot.conf"
#~(begin
(call-with-output-file #$output
(lambda (port)
(format port "server:\n")
(format port " rundir: ~a\n" #$(knot-configuration-run-directory config))
(format port " user: knot\n")
(format port " listen: ~a@~a\n"
#$(knot-configuration-listen-v4 config)
#$(knot-configuration-listen-port config))
(format port " listen: ~a@~a\n"
#$(knot-configuration-listen-v6 config)
#$(knot-configuration-listen-port config))
(format port "\nkey:\n")
(format port #$(knot-key-config (knot-configuration-keys config)))
(format port "\nkeystore:\n")
(format port #$(knot-keystore-config (knot-configuration-keystores config)))
(format port "\nacl:\n")
(format port #$(knot-acl-config (knot-configuration-acls config)))
(format port "\nremote:\n")
(format port #$(knot-remote-config (knot-configuration-remotes config)))
(format port "\npolicy:\n")
(format port #$(knot-policy-config (knot-configuration-policies config)))
(unless #$(eq? (knot-configuration-zones config) '())
(format port "\nzone:\n")
(format port "~a\n"
(string-concatenate
(list #$@(map knot-zone-config
(knot-configuration-zones config)))))))))))
(define %knot-accounts
(list (user-group (name "knot") (system? #t))
(user-account
(name "knot")
(group "knot")
(system? #t)
(comment "knot dns server user")
(home-directory "/var/empty")
(shell (file-append shadow "/sbin/nologin")))))
(define (knot-activation config)
#~(begin
(use-modules (guix build utils))
(define (mkdir-p/perms directory owner perms)
(mkdir-p directory)
(chown directory (passwd:uid owner) (passwd:gid owner))
(chmod directory perms))
(mkdir-p/perms #$(knot-configuration-run-directory config)
(getpwnam "knot") #o755)
(mkdir-p/perms "/var/lib/knot" (getpwnam "knot") #o755)
(mkdir-p/perms "/var/lib/knot/keys" (getpwnam "knot") #o755)
(mkdir-p/perms "/var/lib/knot/keys/keys" (getpwnam "knot") #o755)))
(define (knot-shepherd-service config)
(let* ((config-file (knot-config-file config))
(knot (knot-configuration-knot config)))
(list (shepherd-service
(documentation "Run the Knot DNS daemon.")
(provision '(knot dns))
(requirement '(networking))
(start #~(make-forkexec-constructor
(list (string-append #$knot "/sbin/knotd")
"-c" #$config-file)))
(stop #~(make-kill-destructor))))))
(define knot-service-type
(service-type (name 'knot)
(extensions
(list (service-extension shepherd-root-service-type
knot-shepherd-service)
(service-extension activation-service-type
knot-activation)
(service-extension account-service-type
(const %knot-accounts))))))