* doc/guix.texi (Kerberos Services)[Krb5 Service]: New subsubheading. * gnu/services/kerberos.scm (krb5-service-type): New variable. * gnu/services/configuration.scm (configuration-field-serializer, configuration-field-getter): Export variables. --- doc/guix.texi | 93 ++++++++++ gnu/services/configuration.scm | 2 + gnu/services/kerberos.scm | 377 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 470 insertions(+), 2 deletions(-)
diff --git a/doc/guix.texi b/doc/guix.texi index 3b4ba48..a214354 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -11542,6 +11542,99 @@ remote servers. Run @command{man smtpd.conf} for more information. The @code{(gnu services kerberos)} module provides services relating to the authentication protocol @dfn{Kerberos}. +@subsubheading Krb5 Service + +Programs using Kerberos client library packages normally +expect a configuration file in @file{/etc/krb5.conf}. +This service generates such a file from a definition provided in the +operating system declaration. +It does not cause any daemon to be started. + +No keytab filea are provided by this service. You must explicitly create these. +This service is known to work with the MIT client library, @code{mit-krb5}. +Other implementations have not been tested. + +@defvr {Scheme Variable} krb5-service-type +A service type for Kerberos 5 clients. +@end defvr + +@noindent +Here is an example of its use: +@lisp +(service krb5-service-type + (krb5-configuration + (default-realm "EXAMPLE.COM") + (allow-weak-crypto? #t) + (realms (list + (krb5-realm + (name "EXAMPLE.COM") + (admin-server "groucho.example.com") + (kdc "karl.example.com")) + (krb5-realm + (name "ARGRX.EDU") + (admin-server "kerb-admin.argrx.edu") + (kdc "keys.argrx.edu")))))) +@end lisp + +@noindent +This example provides a Kerberos@tie{}5 client configuration which: +@itemize +@item Recognizes two realms, @i{viz:} ``EXAMPLE.COM'' and ``ARGRX.EDU'', both +of which have distinct administration servers and key distribution centers; +@item Will default to the realm ``EXAMPLE.COM'' if the realm is not explicitly +specified by clients; +@item Accepts services which only support encryption types known to be weak. +@end itemize + +The @code{krb5-realm} and @code{krb5-configuration} types have many fields. +Only the most commonly used ones are described here. +For a full list, and more detailed explanation of each, see the MIT +@uref{http://web.mit.edu/kerberos/krb5-devel/doc/admin/conf_files/krb5_conf.html,,krb5.conf} +documentation. + + +@deftp {Data Type} krb5-realm +@cindex realm, kerberos +@table @asis +@item @code{name} +This field is a string identifying the name of the realm. +A common convention is to use the fully qualified DNS name of your organization, +converted to upper case. + +@item @code{admin-server} +This field is a string identifying the host where the administration server is +running. + +@item @code{kdc} +This field is a string identifying the key distribution center +for the realm. +@end table +@end deftp + +@deftp {Data Type} krb5-configuration + +@table @asis +@item @code{allow-weak-crypto?} (default: @code{#f}) +If this flag is @code{#t} then services which only offer encryption algorithms +known to be weak will be accepted. + +@item @code{default-realm} (default: @code{#f}) +This field should be a string identifying the default Kerberos +realm for the client. +You should set this field to the name of your Kerberos realm. +If this value is @code{#f} +then a realm must be specified with every Kerberos principal when invoking programs +such as @command{kinit}. + +@item @code{realms} +This should be a non-empty list of @code{krb5-realm} objects, which clients may +access. +Normally, one of them will have a @code{name} field matching the @code{default-realm} +field. +@end table +@end deftp + + @subsubheading PAM krb5 Service @cindex pam-krb5 diff --git a/gnu/services/configuration.scm b/gnu/services/configuration.scm index 9f28aab..94c5f21 100644 --- a/gnu/services/configuration.scm +++ b/gnu/services/configuration.scm @@ -30,6 +30,8 @@ configuration-field-name configuration-missing-field configuration-field-error + configuration-field-serializer + configuration-field-getter serialize-configuration define-configuration validate-configuration diff --git a/gnu/services/kerberos.scm b/gnu/services/kerberos.scm index a56f630..b23085d 100644 --- a/gnu/services/kerberos.scm +++ b/gnu/services/kerberos.scm @@ -17,14 +17,387 @@ ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. (define-module (gnu services kerberos) - #:use-module (gnu packages admin) #:use-module (gnu services) + #:use-module (gnu services configuration) #:use-module (gnu system pam) #:use-module (guix gexp) #:use-module (guix records) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-34) + #:use-module (srfi srfi-35) + #:use-module (ice-9 match) #:export (pam-krb5-configuration pam-krb5-configuration? - pam-krb5-service-type)) + pam-krb5-service-type + + krb5-realm + krb5-realm? + + krb5-configuration + krb5-configuration? + krb5-service-type)) + + + +(define unset-field (list 'unset-field)) + +(define (predicate/unset pred) + (lambda (x) (or (eq? x unset-field) (pred x)))) + +(define string/unset? (predicate/unset string?)) +(define boolean/unset? (predicate/unset boolean?)) +(define integer/unset? (predicate/unset integer?)) + +(define (uglify-field-name field-name) + "Return FIELD-NAME with all instances of '-' replaced by '_' and any +trailing '?' removed." + (let ((str (symbol->string field-name))) + (string-join (string-split (if (string-suffix? "?" str) + (substring str 0 (1- (string-length str))) + str) + #\-) + "_"))) + +(define (serialize-field* field-name val) + (format #t "~a = ~a\n" (uglify-field-name field-name) val)) + +(define (serialize-string/unset field-name val) + (if (not (eq? val unset-field)) + (serialize-field* field-name val))) + +(define (serialize-integer/unset field-name val) + (if (not (eq? val unset-field)) + (serialize-field* field-name val))) + +(define (serialize-boolean/unset field-name val) + (if (not (eq? val unset-field)) + (serialize-field* field-name + (if val "true" "false")))) + + +;; An end-point is an address such as "192.168.0.1" +;; or an address port pair ("string/unset.example.com" . 109) +(define (end-point? val) + (or (string? val) + (and (pair? val) + (string? (car val)) + (integer? (cdr val))))) + +(define (serialize-end-point field-name val) + (serialize-field* field-name + (if (string? val) + ;; The [] are needed in the case of IPv6 addresses + (format #f "[~a]" val) + (format #f "[~a]:~a" (car val) (cdr val))))) + + +(define (serialize-space-separated-string-list/unset field-name val) + (if (not (eq? val unset-field)) + (serialize-field* field-name (string-join val " ")))) + +(define space-separated-string-list/unset? + (predicate/unset space-separated-string-list?)) + +(define comma-separated-integer-list/unset? + (predicate/unset (lambda (val) + (and (list? val) + (and-map (lambda (x) (integer? x)) + val))))) + +(define (serialize-comma-separated-integer-list/unset field-name val) + (if (not (eq? val unset-field)) + (serialize-field* field-name + (string-drop ; Drop the leading comma + (fold + (lambda (i prev) + (string-append prev "," (number->string i))) + "" val) 1)))) + +(define file-name? (predicate/unset + (lambda (val) + (string-prefix? "/" val)))) + +(define (serialize-file-name field-name val) + (if (not (eq? val unset-field)) + (serialize-string field-name val))) + +(define (non-negative-integer? val) + (and (exact-integer? val) (not (negative? val)))) + +(define (serialize-non-negative-integer/unset field-name val) + (if (not (eq? val unset-field)) + (serialize-field* field-name val))) + +(define (free-form-fields? val) + (match val + (() #t) + ((((? symbol?) . (? string)) . val) (free-form-fields? val)) + (_ #f))) + +(define (serialize-free-form-fields field-name val) + (for-each (match-lambda ((k . v) (serialize-field* k v))) val)) + +(define non-negative-integer/unset? (predicate/unset non-negative-integer?)) + +(define (realm-list? val) + (and (list? val) + (and-map (lambda (x) (krb5-realm? x)) val))) + +(define (serialize-realm-list field-name val) + (format #t "\n[~a]\n" field-name) + (for-each (lambda (realm) + (format #t "\n~a = {\n" (krb5-realm-name realm)) + (for-each (lambda (field) + (unless (eq? 'name (configuration-field-name field)) + ((configuration-field-serializer field) + (configuration-field-name field) + ((configuration-field-getter field) + realm)))) krb5-realm-fields) + + (format #t "}\n")) val)) + + + +;; For a more detailed explanation of these fields see man 5 krb5.conf +(define-configuration krb5-realm + (name + (string/unset unset-field) + "The name of the realm.") + + (kdc + (end-point unset-field) + "The host and port on which the realm's Key Distribution Server listens.") + + (admin-server + (string/unset unset-field) + "The Host running the administration server for the realm.") + + (master-kdc + (string/unset unset-field) + "If an attempt to get credentials fails because of an invalid password, +the client software will attempt to contact the master KDC.") + + (kpasswd-server + (string/unset unset-field) + "The server where password changes are performed.") + + (auth-to-local + (free-form-fields '()) + "Rules to map between principals and local users.") + + (auth-to-local-names + (free-form-fields '()) + "Explicit mappings between principal names and local user names.") + + (http-anchors + (free-form-fields '()) + "Useful only when http proxy is used to access KDC or KPASSWD.") + + ;; The following are useful only for working with V4 services + (default-domain + (string/unset unset-field) + "The domain used to expand host names when translating Kerberos 4 service +principals to Kerberos 5 principals") + + (v4-instance-convert + (free-form-fields '()) + "Exceptions to the default-domain mapping rule.") + + (v4-realm + (string/unset unset-field) + "Used when the V4 realm name and the V5 realm name are not the same, but +still share the same principal names and passwords")) + + + +;; For a more detailed explanation of these fields see man 5 krb5.conf +(define-configuration krb5-configuration + (allow-weak-crypto? + (boolean/unset unset-field) + "If true, permits access to services which only offer weak encryption.") + + (ap-req-checksum-type + (non-negative-integer/unset unset-field) + "The type of the AP-REQ checksum.") + + (canonicalize? + (boolean/unset unset-field) + "Should principals in initial ticket requests be canonicalized?") + + (ccache-type + (non-negative-integer/unset unset-field) + "The format of the credential cache type.") + + (clockskew + (non-negative-integer/unset unset-field) + "Maximum allowable clock skew in seconds (default 300).") + + (default-ccache-name + (file-name unset-field) + "The name of the default credential cache.") + + (default-client-keytab-name + (file-name unset-field) + "The name of the default keytab for client credentials.") + + (default-keytab-name + (file-name unset-field) + "The name of the default keytab file.") + + (default-realm + (string/unset unset-field) + "The realm to be accessed if not explicitly specified by clients.") + + (default-tgs-enctypes + (free-form-fields '()) + "Session key encryption types when making TGS-REQ requests.") + + (default-tkt-enctypes + (free-form-fields '()) + "Session key encryption types when making AS-REQ requests.") + + (dns-canonicalize-hostname? + (boolean/unset unset-field) + "Whether name lookups will be used to canonicalize host names for use in +service principal names.") + + (dns-lookup-kdc? + (boolean/unset unset-field) + "Should DNS SRV records should be used to locate the KDCs and other servers +not appearing in the realm specification") + + (err-fmt + (string/unset unset-field) + "Custom error message formatting. If not #f error messages will be formatted +by substituting a normal error message for %M and an error code for %C in the +value.") + + (forwardable? + (boolean/unset unset-field) + "Should initial tickets be forwardable by default?") + + (ignore-acceptor-hostname? + (boolean/unset unset-field) + "When accepting GSSAPI or krb5 security contexts for host-based service +principals, ignore any hostname passed by the calling application, and allow +clients to authenticate to any service principal in the keytab matching the +service name and realm name.") + + (k5login-authoritative? + (boolean/unset unset-field) + "If this flag is true, principals must be listed in a local user's k5login +file to be granted login access, if a ~/.k5login file exists.") + + (k5login-directory + (string/unset unset-field) + "If not #f, the library will look for a local user's @file{k5login} file +within the named directory (instead of the user's home directory), with a +file name corresponding to the local user name.") + + (kcm-mach-service + (string/unset unset-field) + "The name of the bootstrap service used to contact the KCM daemon for the +KCM credential cache type.") + + (kcm-socket + (file-name unset-field) + "Path to the Unix domain socket used to access the KCM daemon for the KCM +credential cache type.") + + (kdc-default-options + (non-negative-integer/unset unset-field) + "Default KDC options (logored for multiple values) when requesting initial +tickets.") + + (kdc-timesync + (non-negative-integer/unset unset-field) + "Attempt to compensate for clock skew between the KDC and client.") + + (kdc-req-checksum-type + (non-negative-integer/unset unset-field) + "The type of checksum to use for the KDC requests. Relevant only for DES +keys") + + (noaddresses? + (boolean/unset unset-field) + "If true, initial ticket requests will not be made with address restrictions. +This enables their use across NATs.") + + (permitted-enctypes + (space-separated-string-list/unset unset-field) + "All encryption types that are permitted for use in session key encryption.") + + (plugin-base-dir + (file-name unset-field) + "The directory where krb5 plugins are located.") + + (preferred-preauth-types + (comma-separated-integer-list/unset unset-field) + "The preferred pre-authentication types which the client will attempt before +others.") + + (proxiable? + (boolean/unset unset-field) + "Should initial tickets be proxiable by default?") + + (rdns? + (boolean/unset unset-field) + "Should reverse DNS lookup be used in addition to forward name lookup to +canonicalize host names for use in service principal names.") + + (realm-try-domains + (integer/unset unset-field) + "Should a host's domain components should be used to determine the Kerberos +realm of the host.") + + (renew-lifetime + (non-negative-integer/unset unset-field) + "The default renewable lifetime for initial ticket requests.") + + (safe-checksum-type + (non-negative-integer/unset unset-field) + "The type of checksum to use for the KRB-SAFE requests.") + + (ticket-lifetime + (non-negative-integer/unset unset-field) + "The default lifetime for initial ticket requests.") + + (udp-preference-limit + (non-negative-integer/unset unset-field) + "When sending messages to the KDC, the library will try using TCP +before UDP if the size of the message greater than this limit.") + + (verify-ap-rereq-nofail? + (boolean/unset unset-field) + "If true, then attempts to verify initial credentials will fail if the client +machine does not have a keytab.") + + (realms + (realm-list '()) + "The list of realms which clients may access.")) + + +(define (krb5-configuration-file config) + "Create a Kerberos 5 configuration file based on CONFIG" + (mixed-text-file "krb5.conf" + "[libdefaults]\n\n" + (with-output-to-string + (lambda () + (serialize-configuration config + krb5-configuration-fields))))) + +(define (krb5-etc-service config) + (list `("krb5.conf" ,(krb5-configuration-file config)))) + + +(define krb5-service-type + (service-type (name 'krb5) + (extensions + (list (service-extension etc-service-type + krb5-etc-service))))) + + + (define-record-type* <pam-krb5-configuration> pam-krb5-configuration make-pam-krb5-configuration -- 2.1.4