diff mbox series

[RFC,5/5] NFSD: Handle new xprtsec= export option

Message ID 167932229302.3131.3108041458819604050.stgit@manet.1015granger.net (mailing list archive)
State New, archived
Headers show
Series NFSD support for RPC-with-TLS | expand

Commit Message

Chuck Lever March 20, 2023, 2:24 p.m. UTC
From: Chuck Lever <chuck.lever@oracle.com>

Enable administrators to require clients to use transport layer
security when accessing particular exports.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 fs/nfsd/export.c |   53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
 fs/nfsd/export.h |   11 +++++++++++
 2 files changed, 61 insertions(+), 3 deletions(-)

Comments

Jeff Layton March 21, 2023, 11:50 a.m. UTC | #1
On Mon, 2023-03-20 at 10:24 -0400, Chuck Lever wrote:
> From: Chuck Lever <chuck.lever@oracle.com>
> 
> Enable administrators to require clients to use transport layer
> security when accessing particular exports.

> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
> ---
>  fs/nfsd/export.c |   53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
>  fs/nfsd/export.h |   11 +++++++++++
>  2 files changed, 61 insertions(+), 3 deletions(-)
> 
> diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
> index 668c7527b17e..171ebc21bf07 100644
> --- a/fs/nfsd/export.c
> +++ b/fs/nfsd/export.c
> @@ -439,7 +439,6 @@ static int check_export(struct path *path, int *flags, unsigned char *uuid)
>  		return -EINVAL;
>  	}
>  	return 0;
> -
>  }
>  
>  #ifdef CONFIG_NFSD_V4
> @@ -546,6 +545,31 @@ static inline int
>  secinfo_parse(char **mesg, char *buf, struct svc_export *exp) { return 0; }
>  #endif
>  
> +static int xprtsec_parse(char **mesg, char *buf, struct svc_export *exp)
> +{
> +	unsigned int i, mode, listsize;
> +	int err;
> +
> +	err = get_uint(mesg, &listsize);
> +	if (err)
> +		return err;
> +	if (listsize > 3)
> +		return -EINVAL;

Might want to make a note that the limit of 3 here is arbitrary, and
that it might need to be lifted in the future (if/when we grow other
xprtsec options).

> +
> +	exp->ex_xprtsec_modes = 0;
> +	for (i = 0; i < listsize; i++) {
> +		err = get_uint(mesg, &mode);
> +		if (err)
> +			return err;
> +		mode--;
> +		if (mode > 2)
> +			return -EINVAL;
> +		/* Ad hoc */
> +		exp->ex_xprtsec_modes |= 1 << mode;
> +	}
> +	return 0;
> +}
> +
>  static inline int
>  nfsd_uuid_parse(char **mesg, char *buf, unsigned char **puuid)
>  {
> @@ -608,6 +632,7 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
>  	exp.ex_client = dom;
>  	exp.cd = cd;
>  	exp.ex_devid_map = NULL;
> +	exp.ex_xprtsec_modes = NFSEXP_XPRTSEC_ALL;
>  
>  	/* expiry */
>  	err = -EINVAL;
> @@ -650,6 +675,8 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
>  				err = nfsd_uuid_parse(&mesg, buf, &exp.ex_uuid);
>  			else if (strcmp(buf, "secinfo") == 0)
>  				err = secinfo_parse(&mesg, buf, &exp);
> +			else if (strcmp(buf, "xprtsec") == 0)
> +				err = xprtsec_parse(&mesg, buf, &exp);
>  			else
>  				/* quietly ignore unknown words and anything
>  				 * following. Newer user-space can try to set
> @@ -663,6 +690,7 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
>  		err = check_export(&exp.ex_path, &exp.ex_flags, exp.ex_uuid);
>  		if (err)
>  			goto out4;
> +
>  		/*
>  		 * No point caching this if it would immediately expire.
>  		 * Also, this protects exportfs's dummy export from the
> @@ -824,6 +852,7 @@ static void export_update(struct cache_head *cnew, struct cache_head *citem)
>  	for (i = 0; i < MAX_SECINFO_LIST; i++) {
>  		new->ex_flavors[i] = item->ex_flavors[i];
>  	}
> +	new->ex_xprtsec_modes = item->ex_xprtsec_modes;
>  }
>  
>  static struct cache_head *svc_export_alloc(void)
> @@ -1035,9 +1064,26 @@ static struct svc_export *exp_find(struct cache_detail *cd,
>  
>  __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
>  {
> -	struct exp_flavor_info *f;
> -	struct exp_flavor_info *end = exp->ex_flavors + exp->ex_nflavors;
> +	struct exp_flavor_info *f, *end = exp->ex_flavors + exp->ex_nflavors;
> +	struct svc_xprt *xprt = rqstp->rq_xprt;
> +
> +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_NONE) {
> +		if (!test_bit(XPT_TLS_SESSION, &xprt->xpt_flags))
> +			goto ok;
> +	}
> +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_TLS) {
> +		if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
> +		    !test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
> +			goto ok;
> +	}
> +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_MTLS) {
> +		if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
> +		    test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
> +			goto ok;
> +	}
> +	goto denied;
>  
> +ok:
>  	/* legacy gss-only clients are always OK: */
>  	if (exp->ex_client == rqstp->rq_gssclient)
>  		return 0;
> @@ -1062,6 +1108,7 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
>  	if (nfsd4_spo_must_allow(rqstp))
>  		return 0;
>  
> +denied:
>  	return rqstp->rq_vers < 4 ? nfserr_acces : nfserr_wrongsec;
>  }
>  
> diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h
> index d03f7f6a8642..61e1e8383c3d 100644
> --- a/fs/nfsd/export.h
> +++ b/fs/nfsd/export.h
> @@ -77,8 +77,19 @@ struct svc_export {
>  	struct cache_detail	*cd;
>  	struct rcu_head		ex_rcu;
>  	struct export_stats	ex_stats;
> +	unsigned long		ex_xprtsec_modes;
>  };
>  
> +enum {
> +	NFSEXP_XPRTSEC_NONE	= 0x01,
> +	NFSEXP_XPRTSEC_TLS	= 0x02,
> +	NFSEXP_XPRTSEC_MTLS	= 0x04,
> +};
> +
> +#define NFSEXP_XPRTSEC_ALL	(NFSEXP_XPRTSEC_NONE | \
> +				 NFSEXP_XPRTSEC_TLS | \
> +				 NFSEXP_XPRTSEC_MTLS)
> +
>  /* an "export key" (expkey) maps a filehandlefragement to an
>   * svc_export for a given client.  There can be several per export,
>   * for the different fsid types.
> 
>
Chuck Lever March 21, 2023, 2:05 p.m. UTC | #2
> On Mar 21, 2023, at 7:50 AM, Jeff Layton <jlayton@kernel.org> wrote:
> 
> On Mon, 2023-03-20 at 10:24 -0400, Chuck Lever wrote:
>> From: Chuck Lever <chuck.lever@oracle.com>
>> 
>> Enable administrators to require clients to use transport layer
>> security when accessing particular exports.
> 
>> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
>> ---
>> fs/nfsd/export.c |   53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
>> fs/nfsd/export.h |   11 +++++++++++
>> 2 files changed, 61 insertions(+), 3 deletions(-)
>> 
>> diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
>> index 668c7527b17e..171ebc21bf07 100644
>> --- a/fs/nfsd/export.c
>> +++ b/fs/nfsd/export.c
>> @@ -439,7 +439,6 @@ static int check_export(struct path *path, int *flags, unsigned char *uuid)
>> 		return -EINVAL;
>> 	}
>> 	return 0;
>> -
>> }
>> 
>> #ifdef CONFIG_NFSD_V4
>> @@ -546,6 +545,31 @@ static inline int
>> secinfo_parse(char **mesg, char *buf, struct svc_export *exp) { return 0; }
>> #endif
>> 
>> +static int xprtsec_parse(char **mesg, char *buf, struct svc_export *exp)
>> +{
>> +	unsigned int i, mode, listsize;
>> +	int err;
>> +
>> +	err = get_uint(mesg, &listsize);
>> +	if (err)
>> +		return err;
>> +	if (listsize > 3)
>> +		return -EINVAL;
> 
> Might want to make a note that the limit of 3 here is arbitrary, and
> that it might need to be lifted in the future (if/when we grow other
> xprtsec options).

Well I can easily add a symbolic constant for that too. I
missed this one in the final clean-up before posting.

The bigger question is whether the new downcall parameter is
sensible. If there's a nicer way for mountd to get this
information to the kernel, I'm open to suggestion.


>> +
>> +	exp->ex_xprtsec_modes = 0;
>> +	for (i = 0; i < listsize; i++) {
>> +		err = get_uint(mesg, &mode);
>> +		if (err)
>> +			return err;
>> +		mode--;
>> +		if (mode > 2)
>> +			return -EINVAL;
>> +		/* Ad hoc */
>> +		exp->ex_xprtsec_modes |= 1 << mode;
>> +	}
>> +	return 0;
>> +}
>> +
>> static inline int
>> nfsd_uuid_parse(char **mesg, char *buf, unsigned char **puuid)
>> {
>> @@ -608,6 +632,7 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
>> 	exp.ex_client = dom;
>> 	exp.cd = cd;
>> 	exp.ex_devid_map = NULL;
>> +	exp.ex_xprtsec_modes = NFSEXP_XPRTSEC_ALL;
>> 
>> 	/* expiry */
>> 	err = -EINVAL;
>> @@ -650,6 +675,8 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
>> 				err = nfsd_uuid_parse(&mesg, buf, &exp.ex_uuid);
>> 			else if (strcmp(buf, "secinfo") == 0)
>> 				err = secinfo_parse(&mesg, buf, &exp);
>> +			else if (strcmp(buf, "xprtsec") == 0)
>> +				err = xprtsec_parse(&mesg, buf, &exp);
>> 			else
>> 				/* quietly ignore unknown words and anything
>> 				 * following. Newer user-space can try to set
>> @@ -663,6 +690,7 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
>> 		err = check_export(&exp.ex_path, &exp.ex_flags, exp.ex_uuid);
>> 		if (err)
>> 			goto out4;
>> +
>> 		/*
>> 		 * No point caching this if it would immediately expire.
>> 		 * Also, this protects exportfs's dummy export from the
>> @@ -824,6 +852,7 @@ static void export_update(struct cache_head *cnew, struct cache_head *citem)
>> 	for (i = 0; i < MAX_SECINFO_LIST; i++) {
>> 		new->ex_flavors[i] = item->ex_flavors[i];
>> 	}
>> +	new->ex_xprtsec_modes = item->ex_xprtsec_modes;
>> }
>> 
>> static struct cache_head *svc_export_alloc(void)
>> @@ -1035,9 +1064,26 @@ static struct svc_export *exp_find(struct cache_detail *cd,
>> 
>> __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
>> {
>> -	struct exp_flavor_info *f;
>> -	struct exp_flavor_info *end = exp->ex_flavors + exp->ex_nflavors;
>> +	struct exp_flavor_info *f, *end = exp->ex_flavors + exp->ex_nflavors;
>> +	struct svc_xprt *xprt = rqstp->rq_xprt;
>> +
>> +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_NONE) {
>> +		if (!test_bit(XPT_TLS_SESSION, &xprt->xpt_flags))
>> +			goto ok;
>> +	}
>> +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_TLS) {
>> +		if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
>> +		    !test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
>> +			goto ok;
>> +	}
>> +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_MTLS) {
>> +		if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
>> +		    test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
>> +			goto ok;
>> +	}
>> +	goto denied;
>> 
>> +ok:
>> 	/* legacy gss-only clients are always OK: */
>> 	if (exp->ex_client == rqstp->rq_gssclient)
>> 		return 0;
>> @@ -1062,6 +1108,7 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
>> 	if (nfsd4_spo_must_allow(rqstp))
>> 		return 0;
>> 
>> +denied:
>> 	return rqstp->rq_vers < 4 ? nfserr_acces : nfserr_wrongsec;
>> }
>> 
>> diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h
>> index d03f7f6a8642..61e1e8383c3d 100644
>> --- a/fs/nfsd/export.h
>> +++ b/fs/nfsd/export.h
>> @@ -77,8 +77,19 @@ struct svc_export {
>> 	struct cache_detail	*cd;
>> 	struct rcu_head		ex_rcu;
>> 	struct export_stats	ex_stats;
>> +	unsigned long		ex_xprtsec_modes;
>> };
>> 
>> +enum {
>> +	NFSEXP_XPRTSEC_NONE	= 0x01,
>> +	NFSEXP_XPRTSEC_TLS	= 0x02,
>> +	NFSEXP_XPRTSEC_MTLS	= 0x04,
>> +};
>> +
>> +#define NFSEXP_XPRTSEC_ALL	(NFSEXP_XPRTSEC_NONE | \
>> +				 NFSEXP_XPRTSEC_TLS | \
>> +				 NFSEXP_XPRTSEC_MTLS)
>> +
>> /* an "export key" (expkey) maps a filehandlefragement to an
>>  * svc_export for a given client.  There can be several per export,
>>  * for the different fsid types.
>> 
>> 
> 
> -- 
> Jeff Layton <jlayton@kernel.org>
> 

--
Chuck Lever
Jeff Layton March 21, 2023, 3:10 p.m. UTC | #3
On Tue, 2023-03-21 at 14:05 +0000, Chuck Lever III wrote:
> 
> > On Mar 21, 2023, at 7:50 AM, Jeff Layton <jlayton@kernel.org> wrote:
> > 
> > On Mon, 2023-03-20 at 10:24 -0400, Chuck Lever wrote:
> > > From: Chuck Lever <chuck.lever@oracle.com>
> > > 
> > > Enable administrators to require clients to use transport layer
> > > security when accessing particular exports.
> > 
> > > Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
> > > ---
> > > fs/nfsd/export.c |   53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
> > > fs/nfsd/export.h |   11 +++++++++++
> > > 2 files changed, 61 insertions(+), 3 deletions(-)
> > > 
> > > diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
> > > index 668c7527b17e..171ebc21bf07 100644
> > > --- a/fs/nfsd/export.c
> > > +++ b/fs/nfsd/export.c
> > > @@ -439,7 +439,6 @@ static int check_export(struct path *path, int *flags, unsigned char *uuid)
> > > 		return -EINVAL;
> > > 	}
> > > 	return 0;
> > > -
> > > }
> > > 
> > > #ifdef CONFIG_NFSD_V4
> > > @@ -546,6 +545,31 @@ static inline int
> > > secinfo_parse(char **mesg, char *buf, struct svc_export *exp) { return 0; }
> > > #endif
> > > 
> > > +static int xprtsec_parse(char **mesg, char *buf, struct svc_export *exp)
> > > +{
> > > +	unsigned int i, mode, listsize;
> > > +	int err;
> > > +
> > > +	err = get_uint(mesg, &listsize);
> > > +	if (err)
> > > +		return err;
> > > +	if (listsize > 3)
> > > +		return -EINVAL;
> > 
> > Might want to make a note that the limit of 3 here is arbitrary, and
> > that it might need to be lifted in the future (if/when we grow other
> > xprtsec options).
> 
> Well I can easily add a symbolic constant for that too. I
> missed this one in the final clean-up before posting.
> 
> The bigger question is whether the new downcall parameter is
> sensible. If there's a nicer way for mountd to get this
> information to the kernel, I'm open to suggestion.
> 

I don't know of one. Export options seem fine here, since that's how we
control all sorts of options in the nfs server.

> 
> > > +
> > > +	exp->ex_xprtsec_modes = 0;
> > > +	for (i = 0; i < listsize; i++) {
> > > +		err = get_uint(mesg, &mode);
> > > +		if (err)
> > > +			return err;
> > > +		mode--;
> > > +		if (mode > 2)
> > > +			return -EINVAL;
> > > +		/* Ad hoc */
> > > +		exp->ex_xprtsec_modes |= 1 << mode;
> > > +	}
> > > +	return 0;
> > > +}
> > > +
> > > static inline int
> > > nfsd_uuid_parse(char **mesg, char *buf, unsigned char **puuid)
> > > {
> > > @@ -608,6 +632,7 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
> > > 	exp.ex_client = dom;
> > > 	exp.cd = cd;
> > > 	exp.ex_devid_map = NULL;
> > > +	exp.ex_xprtsec_modes = NFSEXP_XPRTSEC_ALL;
> > > 
> > > 	/* expiry */
> > > 	err = -EINVAL;
> > > @@ -650,6 +675,8 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
> > > 				err = nfsd_uuid_parse(&mesg, buf, &exp.ex_uuid);
> > > 			else if (strcmp(buf, "secinfo") == 0)
> > > 				err = secinfo_parse(&mesg, buf, &exp);
> > > +			else if (strcmp(buf, "xprtsec") == 0)
> > > +				err = xprtsec_parse(&mesg, buf, &exp);
> > > 			else
> > > 				/* quietly ignore unknown words and anything
> > > 				 * following. Newer user-space can try to set
> > > @@ -663,6 +690,7 @@ static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
> > > 		err = check_export(&exp.ex_path, &exp.ex_flags, exp.ex_uuid);
> > > 		if (err)
> > > 			goto out4;
> > > +
> > > 		/*
> > > 		 * No point caching this if it would immediately expire.
> > > 		 * Also, this protects exportfs's dummy export from the
> > > @@ -824,6 +852,7 @@ static void export_update(struct cache_head *cnew, struct cache_head *citem)
> > > 	for (i = 0; i < MAX_SECINFO_LIST; i++) {
> > > 		new->ex_flavors[i] = item->ex_flavors[i];
> > > 	}
> > > +	new->ex_xprtsec_modes = item->ex_xprtsec_modes;
> > > }
> > > 
> > > static struct cache_head *svc_export_alloc(void)
> > > @@ -1035,9 +1064,26 @@ static struct svc_export *exp_find(struct cache_detail *cd,
> > > 
> > > __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
> > > {
> > > -	struct exp_flavor_info *f;
> > > -	struct exp_flavor_info *end = exp->ex_flavors + exp->ex_nflavors;
> > > +	struct exp_flavor_info *f, *end = exp->ex_flavors + exp->ex_nflavors;
> > > +	struct svc_xprt *xprt = rqstp->rq_xprt;
> > > +
> > > +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_NONE) {
> > > +		if (!test_bit(XPT_TLS_SESSION, &xprt->xpt_flags))
> > > +			goto ok;
> > > +	}
> > > +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_TLS) {
> > > +		if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
> > > +		    !test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
> > > +			goto ok;
> > > +	}
> > > +	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_MTLS) {
> > > +		if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
> > > +		    test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
> > > +			goto ok;
> > > +	}
> > > +	goto denied;
> > > 
> > > +ok:
> > > 	/* legacy gss-only clients are always OK: */
> > > 	if (exp->ex_client == rqstp->rq_gssclient)
> > > 		return 0;
> > > @@ -1062,6 +1108,7 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
> > > 	if (nfsd4_spo_must_allow(rqstp))
> > > 		return 0;
> > > 
> > > +denied:
> > > 	return rqstp->rq_vers < 4 ? nfserr_acces : nfserr_wrongsec;
> > > }
> > > 
> > > diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h
> > > index d03f7f6a8642..61e1e8383c3d 100644
> > > --- a/fs/nfsd/export.h
> > > +++ b/fs/nfsd/export.h
> > > @@ -77,8 +77,19 @@ struct svc_export {
> > > 	struct cache_detail	*cd;
> > > 	struct rcu_head		ex_rcu;
> > > 	struct export_stats	ex_stats;
> > > +	unsigned long		ex_xprtsec_modes;
> > > };
> > > 
> > > +enum {
> > > +	NFSEXP_XPRTSEC_NONE	= 0x01,
> > > +	NFSEXP_XPRTSEC_TLS	= 0x02,
> > > +	NFSEXP_XPRTSEC_MTLS	= 0x04,
> > > +};
> > > +
> > > +#define NFSEXP_XPRTSEC_ALL	(NFSEXP_XPRTSEC_NONE | \
> > > +				 NFSEXP_XPRTSEC_TLS | \
> > > +				 NFSEXP_XPRTSEC_MTLS)
> > > +
> > > /* an "export key" (expkey) maps a filehandlefragement to an
> > >  * svc_export for a given client.  There can be several per export,
> > >  * for the different fsid types.
> > > 
> > > 
> > 
> > -- 
> > Jeff Layton <jlayton@kernel.org>
> > 
> 
> --
> Chuck Lever
> 
>
diff mbox series

Patch

diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index 668c7527b17e..171ebc21bf07 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -439,7 +439,6 @@  static int check_export(struct path *path, int *flags, unsigned char *uuid)
 		return -EINVAL;
 	}
 	return 0;
-
 }
 
 #ifdef CONFIG_NFSD_V4
@@ -546,6 +545,31 @@  static inline int
 secinfo_parse(char **mesg, char *buf, struct svc_export *exp) { return 0; }
 #endif
 
+static int xprtsec_parse(char **mesg, char *buf, struct svc_export *exp)
+{
+	unsigned int i, mode, listsize;
+	int err;
+
+	err = get_uint(mesg, &listsize);
+	if (err)
+		return err;
+	if (listsize > 3)
+		return -EINVAL;
+
+	exp->ex_xprtsec_modes = 0;
+	for (i = 0; i < listsize; i++) {
+		err = get_uint(mesg, &mode);
+		if (err)
+			return err;
+		mode--;
+		if (mode > 2)
+			return -EINVAL;
+		/* Ad hoc */
+		exp->ex_xprtsec_modes |= 1 << mode;
+	}
+	return 0;
+}
+
 static inline int
 nfsd_uuid_parse(char **mesg, char *buf, unsigned char **puuid)
 {
@@ -608,6 +632,7 @@  static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
 	exp.ex_client = dom;
 	exp.cd = cd;
 	exp.ex_devid_map = NULL;
+	exp.ex_xprtsec_modes = NFSEXP_XPRTSEC_ALL;
 
 	/* expiry */
 	err = -EINVAL;
@@ -650,6 +675,8 @@  static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
 				err = nfsd_uuid_parse(&mesg, buf, &exp.ex_uuid);
 			else if (strcmp(buf, "secinfo") == 0)
 				err = secinfo_parse(&mesg, buf, &exp);
+			else if (strcmp(buf, "xprtsec") == 0)
+				err = xprtsec_parse(&mesg, buf, &exp);
 			else
 				/* quietly ignore unknown words and anything
 				 * following. Newer user-space can try to set
@@ -663,6 +690,7 @@  static int svc_export_parse(struct cache_detail *cd, char *mesg, int mlen)
 		err = check_export(&exp.ex_path, &exp.ex_flags, exp.ex_uuid);
 		if (err)
 			goto out4;
+
 		/*
 		 * No point caching this if it would immediately expire.
 		 * Also, this protects exportfs's dummy export from the
@@ -824,6 +852,7 @@  static void export_update(struct cache_head *cnew, struct cache_head *citem)
 	for (i = 0; i < MAX_SECINFO_LIST; i++) {
 		new->ex_flavors[i] = item->ex_flavors[i];
 	}
+	new->ex_xprtsec_modes = item->ex_xprtsec_modes;
 }
 
 static struct cache_head *svc_export_alloc(void)
@@ -1035,9 +1064,26 @@  static struct svc_export *exp_find(struct cache_detail *cd,
 
 __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
 {
-	struct exp_flavor_info *f;
-	struct exp_flavor_info *end = exp->ex_flavors + exp->ex_nflavors;
+	struct exp_flavor_info *f, *end = exp->ex_flavors + exp->ex_nflavors;
+	struct svc_xprt *xprt = rqstp->rq_xprt;
+
+	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_NONE) {
+		if (!test_bit(XPT_TLS_SESSION, &xprt->xpt_flags))
+			goto ok;
+	}
+	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_TLS) {
+		if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
+		    !test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
+			goto ok;
+	}
+	if (exp->ex_xprtsec_modes & NFSEXP_XPRTSEC_MTLS) {
+		if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags) &&
+		    test_bit(XPT_PEER_AUTH, &xprt->xpt_flags))
+			goto ok;
+	}
+	goto denied;
 
+ok:
 	/* legacy gss-only clients are always OK: */
 	if (exp->ex_client == rqstp->rq_gssclient)
 		return 0;
@@ -1062,6 +1108,7 @@  __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp)
 	if (nfsd4_spo_must_allow(rqstp))
 		return 0;
 
+denied:
 	return rqstp->rq_vers < 4 ? nfserr_acces : nfserr_wrongsec;
 }
 
diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h
index d03f7f6a8642..61e1e8383c3d 100644
--- a/fs/nfsd/export.h
+++ b/fs/nfsd/export.h
@@ -77,8 +77,19 @@  struct svc_export {
 	struct cache_detail	*cd;
 	struct rcu_head		ex_rcu;
 	struct export_stats	ex_stats;
+	unsigned long		ex_xprtsec_modes;
 };
 
+enum {
+	NFSEXP_XPRTSEC_NONE	= 0x01,
+	NFSEXP_XPRTSEC_TLS	= 0x02,
+	NFSEXP_XPRTSEC_MTLS	= 0x04,
+};
+
+#define NFSEXP_XPRTSEC_ALL	(NFSEXP_XPRTSEC_NONE | \
+				 NFSEXP_XPRTSEC_TLS | \
+				 NFSEXP_XPRTSEC_MTLS)
+
 /* an "export key" (expkey) maps a filehandlefragement to an
  * svc_export for a given client.  There can be several per export,
  * for the different fsid types.