diff --git a/support/export/cache.c b/support/export/cache.c index a5823e92..396b3b73 100644 --- a/support/export/cache.c +++ b/support/export/cache.c @@ -932,6 +932,7 @@ static void write_fsloc(char **bp, int *blen, struct exportent *ep) release_replicas(servers); } #endif + static void write_secinfo(char **bp, int *blen, struct exportent *ep, int flag_mask) { struct sec_entry *p; @@ -949,7 +950,20 @@ static void write_secinfo(char **bp, int *blen, struct exportent *ep, int flag_m qword_addint(bp, blen, p->flav->fnum); qword_addint(bp, blen, p->flags & flag_mask); } +} + +static void write_xprtsec(char **bp, int *blen, struct exportent *ep) +{ + struct xprtsec_entry *p; + + for (p = ep->e_xprtsec; p->info; p++); + if (p == ep->e_xprtsec) + return; + qword_add(bp, blen, "xprtsec"); + qword_addint(bp, blen, p - ep->e_xprtsec); + for (p = ep->e_xprtsec; p->info; p++) + qword_addint(bp, blen, p->info->number); } static int dump_to_cache(int f, char *buf, int blen, char *domain, @@ -992,6 +1006,7 @@ static int dump_to_cache(int f, char *buf, int blen, char *domain, qword_add(&bp, &blen, "uuid"); qword_addhex(&bp, &blen, u, 16); } + write_xprtsec(&bp, &blen, exp); xlog(D_AUTH, "granted access to %s for %s", path, *domain == '$' ? domain+1 : domain); } else { diff --git a/support/include/nfs/export.h b/support/include/nfs/export.h index 0eca828e..be5867cf 100644 --- a/support/include/nfs/export.h +++ b/support/include/nfs/export.h @@ -40,4 +40,18 @@ #define NFSEXP_OLD_SECINFO_FLAGS (NFSEXP_READONLY | NFSEXP_ROOTSQUASH \ | NFSEXP_ALLSQUASH) +/* + * Transport layer security policies that are permitted to access + * an export + */ +#define NFSEXP_XPRTSEC_NONE 0x0001 +#define NFSEXP_XPRTSEC_TLS 0x0002 +#define NFSEXP_XPRTSEC_MTLS 0x0004 + +#define NFSEXP_XPRTSEC_NUM (3) + +#define NFSEXP_XPRTSEC_ALL (NFSEXP_XPRTSEC_NONE | \ + NFSEXP_XPRTSEC_TLS | \ + NFSEXP_XPRTSEC_MTLS) + #endif /* _NSF_EXPORT_H */ diff --git a/support/include/nfslib.h b/support/include/nfslib.h index 6faba71b..61c19933 100644 --- a/support/include/nfslib.h +++ b/support/include/nfslib.h @@ -62,6 +62,18 @@ struct sec_entry { int flags; }; +#define XPRTSECMODE_COUNT 3 + +struct xprtsec_info { + const char *name; + int number; +}; + +struct xprtsec_entry { + const struct xprtsec_info *info; + int flags; +}; + /* * Data related to a single exports entry as returned by getexportent. * FIXME: export options should probably be parsed at a later time to @@ -83,6 +95,7 @@ struct exportent { char * e_fslocdata; char * e_uuid; struct sec_entry e_secinfo[SECFLAVOR_COUNT+1]; + struct xprtsec_entry e_xprtsec[XPRTSECMODE_COUNT + 1]; unsigned int e_ttl; char * e_realpath; }; @@ -99,6 +112,7 @@ struct rmtabent { void setexportent(char *fname, char *type); struct exportent * getexportent(int,int); void secinfo_show(FILE *fp, struct exportent *ep); +void xprtsecinfo_show(FILE *fp, struct exportent *ep); void putexportent(struct exportent *xep); void endexportent(void); struct exportent * mkexportent(char *hname, char *path, char *opts); diff --git a/support/nfs/exports.c b/support/nfs/exports.c index ec6f8013..d36f7664 100644 --- a/support/nfs/exports.c +++ b/support/nfs/exports.c @@ -99,6 +99,7 @@ static void init_exportent (struct exportent *ee, int fromkernel) ee->e_fslocmethod = FSLOC_NONE; ee->e_fslocdata = NULL; ee->e_secinfo[0].flav = NULL; + ee->e_xprtsec[0].info = NULL; ee->e_nsquids = 0; ee->e_nsqgids = 0; ee->e_uuid = NULL; @@ -122,7 +123,7 @@ getexportent(int fromkernel, int fromexports) if (first || (ok = getexport(exp, sizeof(exp))) == 0) { has_default_opts = 0; has_default_subtree_opts = 0; - + init_exportent(&def_ee, fromkernel); ok = getpath(def_ee.e_path, sizeof(def_ee.e_path)); @@ -146,7 +147,7 @@ getexportent(int fromkernel, int fromexports) if (exp[0] == '-' && !fromkernel) { if (parseopts(exp + 1, &def_ee, 0, &has_default_subtree_opts) < 0) return NULL; - + has_default_opts = 1; ok = getexport(exp, sizeof(exp)); @@ -239,7 +240,6 @@ void secinfo_show(FILE *fp, struct exportent *ep) if (ep->e_secinfo[0].flav == NULL) secinfo_addflavor(find_flavor("sys"), ep); for (p1=ep->e_secinfo; p1->flav; p1=p2) { - fprintf(fp, ",sec=%s", p1->flav->flavour); for (p2=p1+1; (p2->flav != NULL) && (p1->flags == p2->flags); p2++) { @@ -249,6 +249,17 @@ void secinfo_show(FILE *fp, struct exportent *ep) } } +void xprtsecinfo_show(FILE *fp, struct exportent *ep) +{ + struct xprtsec_entry *p1, *p2; + + for (p1 = ep->e_xprtsec; p1->info; p1 = p2) { + fprintf(fp, ",xprtsec=%s", p1->info->name); + for (p2 = p1 + 1; p2->info && (p1->flags == p2->flags); p2++) + fprintf(fp, ":%s", p2->info->name); + } +} + static void fprintpath(FILE *fp, const char *path) { @@ -345,6 +356,7 @@ putexportent(struct exportent *ep) } fprintf(fp, "anonuid=%d,anongid=%d", ep->e_anonuid, ep->e_anongid); secinfo_show(fp, ep); + xprtsecinfo_show(fp, ep); fprintf(fp, ")\n"); } @@ -483,6 +495,75 @@ static unsigned int parse_flavors(char *str, struct exportent *ep) return out; } +static const struct xprtsec_info xprtsec_name2info[] = { + { "none", NFSEXP_XPRTSEC_NONE }, + { "tls", NFSEXP_XPRTSEC_TLS }, + { "mtls", NFSEXP_XPRTSEC_MTLS }, + { NULL, 0 } +}; + +static const struct xprtsec_info *find_xprtsec_info(const char *name) +{ + const struct xprtsec_info *info; + + for (info = xprtsec_name2info; info->name; info++) + if (strcmp(info->name, name) == 0) + return info; + return NULL; +} + +/* + * Append the given xprtsec mode to the exportent's e_xprtsec array, + * or do nothing if it's already there. Returns the index of flavor in + * the resulting array in any case. + */ +static int xprtsec_addmode(const struct xprtsec_info *info, struct exportent *ep) +{ + struct xprtsec_entry *p; + + for (p = ep->e_xprtsec; p->info; p++) + if (p->info == info || p->info->number == info->number) + return p - ep->e_xprtsec; + + if (p - ep->e_xprtsec >= XPRTSECMODE_COUNT) { + xlog(L_ERROR, "more than %d xprtsec modes on an export\n", + XPRTSECMODE_COUNT); + return -1; + } + p->info = info; + p->flags = ep->e_flags; + (p + 1)->info = NULL; + return p - ep->e_xprtsec; +} + +/* + * @str is a colon seperated list of transport layer security modes. + * Their order is recorded in @ep, and a bitmap corresponding to the + * list is returned. + * + * A zero return indicates an error. + */ +static unsigned int parse_xprtsec(char *str, struct exportent *ep) +{ + unsigned int out = 0; + char *name; + + while ((name = strsep(&str, ":"))) { + const struct xprtsec_info *info = find_xprtsec_info(name); + int bit; + + if (!info) { + xlog(L_ERROR, "unknown xprtsec mode %s\n", name); + return 0; + } + bit = xprtsec_addmode(info, ep); + if (bit < 0) + return 0; + out |= 1 << bit; + } + return out; +} + /* Sets the bits in @mask for the appropriate security flavor flags. */ static void setflags(int mask, unsigned int active, struct exportent *ep) { @@ -621,7 +702,7 @@ parseopts(char *cp, struct exportent *ep, int warn, int *had_subtree_opt_ptr) ep->e_anonuid = strtol(opt+8, &oe, 10); if (opt[8]=='\0' || *oe != '\0') { xlog(L_ERROR, "%s: %d: bad anonuid \"%s\"\n", - flname, flline, opt); + flname, flline, opt); bad_option: free(opt); return -1; @@ -631,7 +712,7 @@ bad_option: ep->e_anongid = strtol(opt+8, &oe, 10); if (opt[8]=='\0' || *oe != '\0') { xlog(L_ERROR, "%s: %d: bad anongid \"%s\"\n", - flname, flline, opt); + flname, flline, opt); goto bad_option; } } else if (strncmp(opt, "squash_uids=", 12) == 0) { @@ -649,13 +730,13 @@ bad_option: setflags(NFSEXP_FSID, active, ep); } else { ep->e_fsid = strtoul(opt+5, &oe, 0); - if (opt[5]!='\0' && *oe == '\0') + if (opt[5]!='\0' && *oe == '\0') setflags(NFSEXP_FSID, active, ep); else if (valid_uuid(opt+5)) ep->e_uuid = strdup(opt+5); else { xlog(L_ERROR, "%s: %d: bad fsid \"%s\"\n", - flname, flline, opt); + flname, flline, opt); goto bad_option; } } @@ -688,6 +769,9 @@ bad_option: active = parse_flavors(opt+4, ep); if (!active) goto bad_option; + } else if (strncmp(opt, "xprtsec=", 8) == 0) { + if (!parse_xprtsec(opt + 8, ep)) + goto bad_option; } else { xlog(L_ERROR, "%s:%d: unknown keyword \"%s\"\n", flname, flline, opt); @@ -709,7 +793,7 @@ out: if (warn && !had_subtree_opt) xlog(L_WARNING, "%s [%d]: Neither 'subtree_check' or 'no_subtree_check' specified for export \"%s:%s\".\n" " Assuming default behaviour ('no_subtree_check').\n" - " NOTE: this default has changed since nfs-utils version 1.0.x\n", + " NOTE: this default has changed since nfs-utils version 1.0.x\n", flname, flline, ep->e_hostname, ep->e_path); diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c index 6ba615d1..a87a7806 100644 --- a/utils/exportfs/exportfs.c +++ b/utils/exportfs/exportfs.c @@ -743,6 +743,7 @@ dump(int verbose, int export_format) #endif } secinfo_show(stdout, ep); + xprtsecinfo_show(stdout, ep); printf("%c\n", (c != '(')? ')' : ' '); } } diff --git a/utils/exportfs/exports.man b/utils/exportfs/exports.man index 54b3f877..83dd6807 100644 --- a/utils/exportfs/exports.man +++ b/utils/exportfs/exports.man @@ -125,7 +125,55 @@ In that case you may include multiple sec= options, and following options will be enforced only for access using flavors listed in the immediately preceding sec= option. The only options that are permitted to vary in this way are ro, rw, no_root_squash, root_squash, and all_squash. +.SS Transport layer security +The Linux NFS server allows the use of RPC-with-TLS (RFC 9289) to +protect RPC traffic between itself and its clients. +Alternately, administrators can secure NFS traffic using a VPN, +or an ssh tunnel or similar mechanism, in a way that is transparent +to the server. .PP +To enable the use of RPC-with-TLS, the server's administrator must +install and configure +.BR tlshd +to handle transport layer security handshake requests from the local +kernel. +Clients can then choose to use RPC-with-TLS or they may continue +operating without it. +.PP +Administrators may require the use of RPC-with-TLS to protect access +to individual exports. +This is particularly useful when using non-cryptographic security +flavors such as +.IR sec=sys . +The +.I xprtsec= +option, followed by an unordered colon-delimited list of security policies, +can restrict access to the export to only clients that have negotiated +transport-layer security. +Currently supported transport layer security policies include: +.TP +.IR none +The server permits clients to access the export +without the use of transport layer security. +.TP +.IR tls +The server permits clients that have negotiated an RPC-with-TLS session +without peer authentication (confidentiality only) to access the export. +Clients are not required to offer an x.509 certificate +when establishing a transport layer security session. +.TP +.IR mtls +The server permits clients that have negotiated an RPC-with-TLS session +with peer authentication to access the export. +The server requires clients to offer an x.509 certificate +when establishing a transport layer security session. +.PP +If RPC-with-TLS is configured and enabled and the +.I xprtsec= +option is not specified, the default setting for an export is +.IR xprtsec=none:tls:mtls . +With this setting, the server permits clients to use any transport +layer security mechanism or none at all to access the export. .SS General Options .BR exportfs understands the following export options: @@ -581,7 +629,8 @@ a character class wildcard match. .BR netgroup (5), .BR mountd (8), .BR nfsd (8), -.BR showmount (8). +.BR showmount (8), +.BR tlshd (8). .\".SH DIAGNOSTICS .\"An error parsing the file is reported using syslogd(8) as level NOTICE from .\"a DAEMON whenever diff --git a/utils/mount/nfs.man b/utils/mount/nfs.man index d9f34df3..dfc31a5d 100644 --- a/utils/mount/nfs.man +++ b/utils/mount/nfs.man @@ -574,7 +574,43 @@ The .B sloppy option is an alternative to specifying .BR mount.nfs " -s " option. - +.TP 1.5i +.BI xprtsec= policy +Specifies the use of transport layer security to protect NFS network +traffic on behalf of this mount point. +.I policy +can be one of +.BR none , +.BR tls , +or +.BR mtls . +.IP +If +.B none +is specified, +transport layer security is forced off, even if the NFS server supports +transport layer security. +If +.B tls +is specified, the client uses RPC-with-TLS to provide in-transit +confidentiality. +If +.B mtls +is specified, the client uses RPC-with-TLS to authenticate itself and +to provide in-transit confidentiality. +If either +.B tls +or +.B mtls +is specified and the server does not support RPC-with-TLS or peer +authentication fails, the mount attempt fails. +.IP +If the +.B xprtsec= +option is not specified, +the default behavior depends on the kernel version, +but is usually equivalent to +.BR "xprtsec=none" . .SS "Options for NFS versions 2 and 3 only" Use these options, along with the options in the above subsection, for NFS versions 2 and 3 only.