Add a new "tlscertname" NFS mount option.

When using NFS-over-TLS, an NFS client can optionally provide an X.509
certificate to the server during the TLS handshake.  For some situations,
such as different NFS servers or different certificates being mapped
to different user credentials on the NFS server, there may be a need
for different mounts to provide different certificates.

This new mount option called "tlscertname" may be used to specify a
non-default certificate be provided.  This alernate certificate will
be stored in /etc/rpc.tlsclntd in a file with a name based on what is
provided by this mount option.
This commit is contained in:
Rick Macklem 2020-12-21 15:14:53 -08:00
parent e523262107
commit 665b1365fe
9 changed files with 71 additions and 14 deletions

View File

@ -281,8 +281,12 @@ newnfs_connect(struct nfsmount *nmp, struct nfssockreq *nrp,
CLNT_CONTROL(client, CLSET_INTERRUPTIBLE, &one);
if ((nmp->nm_flag & NFSMNT_RESVPORT))
CLNT_CONTROL(client, CLSET_PRIVPORT, &one);
if (NFSHASTLS(nmp))
if (NFSHASTLS(nmp)) {
CLNT_CONTROL(client, CLSET_TLS, &one);
if (nmp->nm_tlscertname != NULL)
CLNT_CONTROL(client, CLSET_TLSCERTNAME,
nmp->nm_tlscertname);
}
if (NFSHASSOFT(nmp)) {
if (nmp->nm_sotype == SOCK_DGRAM)
/*

View File

@ -119,7 +119,7 @@ static void nfs_decode_args(struct mount *mp, struct nfsmount *nmp,
static int mountnfs(struct nfs_args *, struct mount *,
struct sockaddr *, char *, u_char *, int, u_char *, int,
u_char *, int, struct vnode **, struct ucred *,
struct thread *, int, int, int, uint32_t);
struct thread *, int, int, int, uint32_t, char *);
static void nfs_getnlminfo(struct vnode *, uint8_t *, size_t *,
struct sockaddr_storage *, int *, off_t *,
struct timeval *);
@ -545,7 +545,7 @@ nfs_mountdiskless(char *path,
nam = sodupsockaddr((struct sockaddr *)sin, M_WAITOK);
if ((error = mountnfs(args, mp, nam, path, NULL, 0, dirpath, dirlen,
NULL, 0, vpp, td->td_ucred, td, NFS_DEFAULT_NAMETIMEO,
NFS_DEFAULT_NEGNAMETIMEO, 0, 0)) != 0) {
NFS_DEFAULT_NEGNAMETIMEO, 0, 0, NULL)) != 0) {
printf("nfs_mountroot: mount %s on /: %d\n", path, error);
return (error);
}
@ -747,7 +747,7 @@ static const char *nfs_opts[] = { "from", "nfs_args",
"resvport", "readahead", "hostname", "timeo", "timeout", "addr", "fh",
"nfsv3", "sec", "principal", "nfsv4", "gssname", "allgssname", "dirpath",
"minorversion", "nametimeo", "negnametimeo", "nocto", "noncontigwr",
"pnfs", "wcommitsize", "oneopenown", "tls",
"pnfs", "wcommitsize", "oneopenown", "tls", "tlscertname",
NULL };
/*
@ -891,7 +891,7 @@ nfs_mount(struct mount *mp)
struct thread *td;
char *hst;
u_char nfh[NFSX_FHMAX], krbname[100], dirpath[100], srvkrbname[100];
char *cp, *opt, *name, *secname;
char *cp, *opt, *name, *secname, *tlscertname;
int nametimeo = NFS_DEFAULT_NAMETIMEO;
int negnametimeo = NFS_DEFAULT_NEGNAMETIMEO;
int minvers = 0;
@ -903,6 +903,7 @@ nfs_mount(struct mount *mp)
has_nfs_args_opt = 0;
has_nfs_from_opt = 0;
newflag = 0;
tlscertname = NULL;
hst = malloc(MNAMELEN, M_TEMP, M_WAITOK);
if (vfs_filteropt(mp->mnt_optnew, nfs_opts)) {
error = EINVAL;
@ -988,6 +989,22 @@ nfs_mount(struct mount *mp)
args.flags |= NFSMNT_ONEOPENOWN;
if (vfs_getopt(mp->mnt_optnew, "tls", NULL, NULL) == 0)
newflag |= NFSMNT_TLS;
if (vfs_getopt(mp->mnt_optnew, "tlscertname", (void **)&opt, &len) ==
0) {
/*
* tlscertname with "key.pem" appended to it forms a file
* name. As such, the maximum allowable strlen(tlscertname) is
* NAME_MAX - 7. However, "len" includes the nul termination
* byte so it can be up to NAME_MAX - 6.
*/
if (opt == NULL || len <= 1 || len > NAME_MAX - 6) {
vfs_mount_error(mp, "invalid tlscertname");
error = EINVAL;
goto out;
}
tlscertname = malloc(len, M_NEWNFSMNT, M_WAITOK);
strlcpy(tlscertname, opt, len);
}
if (vfs_getopt(mp->mnt_optnew, "readdirsize", (void **)&opt, NULL) == 0) {
if (opt == NULL) {
vfs_mount_error(mp, "illegal readdirsize");
@ -1342,7 +1359,7 @@ nfs_mount(struct mount *mp)
args.fh = nfh;
error = mountnfs(&args, mp, nam, hst, krbname, krbnamelen, dirpath,
dirlen, srvkrbname, srvkrbnamelen, &vp, td->td_ucred, td,
nametimeo, negnametimeo, minvers, newflag);
nametimeo, negnametimeo, minvers, newflag, tlscertname);
out:
if (!error) {
MNT_ILOCK(mp);
@ -1390,7 +1407,7 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
char *hst, u_char *krbname, int krbnamelen, u_char *dirpath, int dirlen,
u_char *srvkrbname, int srvkrbnamelen, struct vnode **vpp,
struct ucred *cred, struct thread *td, int nametimeo, int negnametimeo,
int minvers, uint32_t newflag)
int minvers, uint32_t newflag, char *tlscertname)
{
struct nfsmount *nmp;
struct nfsnode *np;
@ -1410,6 +1427,7 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
nmp = VFSTONFS(mp);
printf("%s: MNT_UPDATE is no longer handled here\n", __func__);
free(nam, M_SONAME);
free(tlscertname, M_NEWNFSMNT);
return (0);
} else {
/* NFS-over-TLS requires that rpctls be functioning. */
@ -1423,12 +1441,14 @@ mountnfs(struct nfs_args *argp, struct mount *mp, struct sockaddr *nam,
#endif
if (error != 0) {
free(nam, M_SONAME);
free(tlscertname, M_NEWNFSMNT);
return (error);
}
}
nmp = malloc(sizeof (struct nfsmount) +
krbnamelen + dirlen + srvkrbnamelen + 2,
M_NEWNFSMNT, M_WAITOK | M_ZERO);
nmp->nm_tlscertname = tlscertname;
nmp->nm_newflag = newflag;
TAILQ_INIT(&nmp->nm_bufq);
TAILQ_INIT(&nmp->nm_sess);
@ -1681,6 +1701,7 @@ bad:
newnfs_disconnect(dsp->nfsclds_sockp);
nfscl_freenfsclds(dsp);
}
free(nmp->nm_tlscertname, M_NEWNFSMNT);
free(nmp, M_NEWNFSMNT);
free(nam, M_SONAME);
return (error);
@ -1776,6 +1797,7 @@ nfs_unmount(struct mount *mp, int mntflags)
newnfs_disconnect(dsp->nfsclds_sockp);
nfscl_freenfsclds(dsp);
}
free(nmp->nm_tlscertname, M_NEWNFSMNT);
free(nmp, M_NEWNFSMNT);
out:
return (error);

View File

@ -76,6 +76,7 @@ struct nfsmount {
/* Newnfs additions */
TAILQ_HEAD(, nfsclds) nm_sess; /* Session(s) for NFSv4.1. */
struct nfsclclient *nm_clp;
char *nm_tlscertname; /* TLS certificate file name */
uid_t nm_uid; /* Uid for SetClientID etc. */
u_int64_t nm_clval; /* identifies which clientid */
u_int64_t nm_fsid[2]; /* NFSv4 fsid */

View File

@ -359,6 +359,7 @@ enum clnt_stat clnt_call_private(CLIENT *, struct rpc_callextra *, rpcproc_t,
#define CLSET_BACKCHANNEL 29 /* set backchannel for socket */
#define CLSET_TLS 30 /* set TLS for socket */
#define CLSET_BLOCKRCV 31 /* Temporarily block reception */
#define CLSET_TLSCERTNAME 32 /* TLS certificate file name */
#endif

View File

@ -110,6 +110,7 @@ clnt_reconnect_create(
rc->rc_ucred = crdup(curthread->td_ucred);
rc->rc_client = NULL;
rc->rc_tls = false;
rc->rc_tlscertname = NULL;
cl->cl_refs = 1;
cl->cl_ops = &clnt_reconnect_ops;
@ -198,7 +199,8 @@ clnt_reconnect_connect(CLIENT *cl)
(struct sockaddr *) &rc->rc_addr, rc->rc_prog, rc->rc_vers,
rc->rc_sendsz, rc->rc_recvsz, rc->rc_intr);
if (rc->rc_tls && newclient != NULL) {
stat = rpctls_connect(newclient, so, ssl, &reterr);
stat = rpctls_connect(newclient, rc->rc_tlscertname, so,
ssl, &reterr);
if (stat != RPC_SUCCESS || reterr != RPCTLSERR_OK) {
if (stat == RPC_SUCCESS)
stat = RPC_FAILED;
@ -405,6 +407,7 @@ clnt_reconnect_control(CLIENT *cl, u_int request, void *info)
{
struct rc_data *rc = (struct rc_data *)cl->cl_private;
SVCXPRT *xprt;
size_t slen;
if (info == NULL) {
return (FALSE);
@ -496,6 +499,20 @@ clnt_reconnect_control(CLIENT *cl, u_int request, void *info)
rc->rc_tls = true;
break;
case CLSET_TLSCERTNAME:
slen = strlen(info) + 1;
/*
* tlscertname with "key.pem" appended to it forms a file
* name. As such, the maximum allowable strlen(info) is
* NAME_MAX - 7. However, "slen" includes the nul termination
* byte so it can be up to NAME_MAX - 6.
*/
if (slen <= 1 || slen > NAME_MAX - 6)
return (FALSE);
rc->rc_tlscertname = mem_alloc(slen);
strlcpy(rc->rc_tlscertname, info, slen);
break;
default:
return (FALSE);
}
@ -543,6 +560,7 @@ clnt_reconnect_destroy(CLIENT *cl)
}
crfree(rc->rc_ucred);
mtx_destroy(&rc->rc_lock);
mem_free(rc->rc_tlscertname, 0); /* 0 ok, since arg. ignored. */
mem_free(rc, sizeof(*rc));
mem_free(cl, sizeof (CLIENT));
}

View File

@ -80,6 +80,7 @@ struct rc_data {
struct rpc_err rc_err;
void *rc_backchannel;
bool rc_tls; /* Enable TLS on connection */
char *rc_tlscertname;
};
/* Bits for ct_rcvstate. */

View File

@ -58,8 +58,8 @@ int rpctls_syscall(int, const char *);
#ifdef _KERNEL
/* Functions that perform upcalls to the rpctlsd daemon. */
enum clnt_stat rpctls_connect(CLIENT *newclient, struct socket *so,
uint64_t *sslp, uint32_t *reterr);
enum clnt_stat rpctls_connect(CLIENT *newclient, char *certname,
struct socket *so, uint64_t *sslp, uint32_t *reterr);
enum clnt_stat rpctls_cl_handlerecord(uint64_t sec, uint64_t usec,
uint64_t ssl, uint32_t *reterr);
enum clnt_stat rpctls_srv_handlerecord(uint64_t sec, uint64_t usec,

View File

@ -356,9 +356,10 @@ rpctls_server_client(void)
/* Do an upcall for a new socket connect using TLS. */
enum clnt_stat
rpctls_connect(CLIENT *newclient, struct socket *so, uint64_t *sslp,
uint32_t *reterr)
rpctls_connect(CLIENT *newclient, char *certname, struct socket *so,
uint64_t *sslp, uint32_t *reterr)
{
struct rpctlscd_connect_arg arg;
struct rpctlscd_connect_res res;
struct rpc_callextra ext;
struct timeval utimeout;
@ -399,7 +400,12 @@ rpctls_connect(CLIENT *newclient, struct socket *so, uint64_t *sslp,
CLNT_CONTROL(newclient, CLSET_BLOCKRCV, &val);
/* Do the connect handshake upcall. */
stat = rpctlscd_connect_1(NULL, &res, cl);
if (certname != NULL) {
arg.certname.certname_len = strlen(certname);
arg.certname.certname_val = certname;
} else
arg.certname.certname_len = 0;
stat = rpctlscd_connect_1(&arg, &res, cl);
if (stat == RPC_SUCCESS) {
*reterr = res.reterr;
if (res.reterr == 0) {

View File

@ -29,6 +29,10 @@
/* $FreeBSD$ */
struct rpctlscd_connect_arg {
char certname<>;
};
struct rpctlscd_connect_res {
uint32_t reterr;
uint64_t sec;
@ -61,7 +65,7 @@ program RPCTLSCD {
void RPCTLSCD_NULL(void) = 0;
rpctlscd_connect_res
RPCTLSCD_CONNECT(void) = 1;
RPCTLSCD_CONNECT(rpctlscd_connect_arg) = 1;
rpctlscd_handlerecord_res
RPCTLSCD_HANDLERECORD(rpctlscd_handlerecord_arg) = 2;