Commit 311f34fe authored by Tigran Mkrtchyan's avatar Tigran Mkrtchyan
Browse files

exports: add support for secure/insecure export options

Motivation:
In some environments we might want to enforce clients to come from a
privileged port (< 1024), e.g. to be sure that request is send by kernel
process.

Modification:
Add secure/insecure export options (like in linux and solaris servers).
Update PseudoFs to to check for privileged client port, if required.

Result:
nfs clients can be restricted to privileged (root) users.

Acked-by: Lea Morschel
Acked-by: Paul Millar
Target: master
parent c6ffcd94
/*
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
* Copyright (c) 2009 - 2020 Deutsches Elektronen-Synchroton,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
*
* This library is free software; you can redistribute it and/or modify
......@@ -283,6 +283,16 @@ public class ExportFile implements ExportTable {
continue;
}
if (option.equals("secure")) {
exportBuilder.withPrivilegedClientPort();
continue;
}
if (option.equals("insecure")) {
exportBuilder.withoutPrivilegedClientPort();
continue;
}
throw new IllegalArgumentException("Unsupported option: " + option);
}
FsExport export = exportBuilder.build(path);
......
......@@ -75,6 +75,7 @@ public class FsExport {
private final int _index;
private final boolean _withPnfs;
private final List<layouttype4> _layoutTypes;
private final boolean _requirePrivilegedClientPort;
/**
* NFS clients may be specified in a number of ways:<br>
......@@ -127,6 +128,7 @@ public class FsExport {
_withPnfs = builder.isWithPnfs();
_index = getExportIndex(_path);
_layoutTypes = List.copyOf(builder.getLayoutTypes());
_requirePrivilegedClientPort = builder.isPrivilegedClientPortRequired();
}
public static int getExportIndex(String path) {
......@@ -156,6 +158,8 @@ public class FsExport {
.append(_withAcl ? "acl" : "noacl")
.append(',')
.append("sec=").append(_sec)
.append(_requirePrivilegedClientPort ? "secure" : "insecure")
.append(",")
.append(',')
.append(_withDcap ? "dcap" : "no_dcap")
.append(',')
......@@ -239,6 +243,10 @@ public class FsExport {
return _withPnfs;
}
public boolean isPrivilegedClientPortRequired() {
return _requirePrivilegedClientPort;
}
/**
* Get an ordered list of layout types to be used by this export entry.
*
......@@ -350,6 +358,7 @@ public class FsExport {
private boolean _allRoot = false;
private boolean _withPnfs = true;
private final List<layouttype4> _layoutTypes = new ArrayList<>();
private boolean _requirePrivilegedClientPort;
public FsExportBuilder forClient(String client) {
checkArgument(isValidHostSpecifier(client), "bad host specifier: " + client);
......@@ -358,6 +367,20 @@ public class FsExport {
return this;
}
public FsExportBuilder withPrivilegedClientPort() {
_requirePrivilegedClientPort = true;
return this;
}
public FsExportBuilder withoutPrivilegedClientPort() {
_requirePrivilegedClientPort = false;
return this;
}
public boolean isPrivilegedClientPortRequired() {
return _requirePrivilegedClientPort;
}
public FsExportBuilder trusted() {
_isTrusted = Root.TRUSTED;
return this;
......
......@@ -21,7 +21,7 @@ package org.dcache.nfs.vfs;
import com.google.common.base.Splitter;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
......@@ -62,8 +62,12 @@ import static org.dcache.nfs.vfs.AclCheckable.Access;
public class PseudoFs extends ForwardingFileSystem {
private final static Logger _log = LoggerFactory.getLogger(PseudoFs.class);
/** TCP port range between 0 and 1023 can be used only by privileged (root) user */
public static final int PRIVILEGED_PORT = 1023;
private final Subject _subject;
private final InetAddress _inetAddress;
private final InetSocketAddress _inetAddress;
private final VirtualFileSystem _inner;
private final ExportTable _exportTable;
private final RpcAuth _auth;
......@@ -77,7 +81,7 @@ public class PseudoFs extends ForwardingFileSystem {
_inner = inner;
_subject = call.getCredential().getSubject();
_auth = call.getCredential();
_inetAddress = call.getTransport().getRemoteSocketAddress().getAddress();
_inetAddress = call.getTransport().getRemoteSocketAddress();
_exportTable = exportTable;
}
......@@ -189,13 +193,13 @@ public class PseudoFs extends ForwardingFileSystem {
/*
* reject if there are no exports for this client at all
*/
if (!_exportTable.exports(_inetAddress).findAny().isPresent()) {
if (!_exportTable.exports(_inetAddress.getAddress()).findAny().isPresent()) {
_log.warn("Access denied: (no export) fs root for client {}", _inetAddress);
throw new AccessException("no exports");
}
Inode inode = _inner.getRootInode();
FsExport export = _exportTable.getExport("/", _inetAddress);
FsExport export = _exportTable.getExport("/", _inetAddress.getAddress());
return export == null? realToPseudo(inode) :
pushExportIndex(inode, export.getIndex());
}
......@@ -211,7 +215,7 @@ public class PseudoFs extends ForwardingFileSystem {
/*
* REVISIT: this is not the best place to do it, but the simples one.
*/
FsExport export = _exportTable.getExport(parent.exportIndex(), _inetAddress);
FsExport export = _exportTable.getExport(parent.exportIndex(), _inetAddress.getAddress());
if (!export.isWithDcap() && ".(get)(cursor)".equals(path)) {
throw new NoEntException("the dcap magic file is blocked");
}
......@@ -427,7 +431,7 @@ public class PseudoFs extends ForwardingFileSystem {
if (!inode.isPseudoInode()) {
int exportIdx = getExportIndex(inode);
FsExport export = _exportTable.getExport(exportIdx, _inetAddress);
FsExport export = _exportTable.getExport(exportIdx, _inetAddress.getAddress());
if (exportIdx != 0 && export == null) {
if (shouldLog) {
_log.warn("Access denied: (no export) to inode {} for client {}", inode, _inetAddress);
......@@ -435,6 +439,12 @@ public class PseudoFs extends ForwardingFileSystem {
throw new AccessException("permission deny");
}
if (export.isPrivilegedClientPortRequired() && _inetAddress.getPort() > PRIVILEGED_PORT) {
if (shouldLog) {
_log.warn("Access denied: unprivileged client {}", _inetAddress);
}
throw new AccessException("unprivileged client");
}
checkSecurityFlavor(_auth, export.getSec());
if ( (export.ioMode() == FsExport.IO.RO) && Acls.wantModify(requestedMask)) {
......@@ -620,7 +630,7 @@ public class PseudoFs extends ForwardingFileSystem {
* This can be wrong, e.g. RO vs. RW.
*/
if (inode.handleVersion() == 0) {
FsExport export = _exportTable.exports(_inetAddress)
FsExport export = _exportTable.exports(_inetAddress.getAddress())
.findFirst()
.orElse(null);
return export == null? -1 : export.getIndex();
......@@ -679,7 +689,7 @@ public class PseudoFs extends ForwardingFileSystem {
Inode rootInode = realToPseudo(_inner.getRootInode());
PseudoFsNode root = new PseudoFsNode(rootInode);
_exportTable.exports(_inetAddress).forEach(e -> pathToPseudoFs(root, nodes, e));
_exportTable.exports(_inetAddress.getAddress()).forEach(e -> pathToPseudoFs(root, nodes, e));
if (nodes.isEmpty()) {
_log.warn("No exports found for: {}", _inetAddress);
......@@ -727,6 +737,6 @@ public class PseudoFs extends ForwardingFileSystem {
}
private boolean inheritUidGid(Inode inode) {
return _exportTable.getExport(inode.exportIndex(), _inetAddress).isAllRoot();
return _exportTable.getExport(inode.exportIndex(), _inetAddress.getAddress()).isAllRoot();
}
}
......@@ -274,4 +274,25 @@ public class FsExportTest {
assertEquals(FsExport.IO.RO, iomode);
}
@Test
public void testSecureOption() throws Exception {
FsExport export = _exportFile.getExport("/secure", InetAddress.getByName("192.168.17.1"));
assertTrue("Incorrect secure option", export.isPrivilegedClientPortRequired());
}
@Test
public void testInsecureOption() throws Exception {
FsExport export = _exportFile.getExport("/insecure", InetAddress.getByName("192.168.17.1"));
assertFalse("Incorrect secure option", export.isPrivilegedClientPortRequired());
}
@Test
public void testDefaultSecureOption() throws Exception {
FsExport export = _exportFile.getExport("/secure_default", InetAddress.getByName("192.168.17.1"));
assertFalse("Incorrect default secure option", export.isPrivilegedClientPortRequired());
}
}
/*
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
* Copyright (c) 2009 - 2020 Deutsches Elektronen-Synchroton,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
*
* This library is free software; you can redistribute it and/or modify
......@@ -56,7 +56,7 @@ import org.junit.Before;
*/
public class PseudoFsTest {
private final static InetSocketAddress localAddress = new InetSocketAddress(0);
private final static InetSocketAddress localAddress = new InetSocketAddress(31415);
private VirtualFileSystem vfs;
private ExportFile mockedExportFile;
private FsExport mockedExport;
......@@ -699,4 +699,48 @@ public class PseudoFsTest {
pseudoFs.setXattr(inode, "xattr1", "value1".getBytes(StandardCharsets.UTF_8), VirtualFileSystem.SetXattrMode.CREATE);
}
@Test(expected = AccessException.class)
public void testUnprivilegedClient() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.ROOT);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
FsExport export = new FsExport.FsExportBuilder()
.rw()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.NONE)
.withPrivilegedClientPort()
.build("/");
given(mockedExportFile.getExport(fsRoot.exportIndex(), localAddress.getAddress())).willReturn(export);
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
pseudoFs.getattr(fsRoot);
}
@Test
public void testPrivilegedClient() throws IOException {
InetSocketAddress privilegedClient = new InetSocketAddress(314);
given(mockedTransport.getRemoteSocketAddress()).willReturn(privilegedClient);
given(mockedAuth.getSubject()).willReturn(Subjects.ROOT);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
FsExport export = new FsExport.FsExportBuilder()
.rw()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.NONE)
.withPrivilegedClientPort()
.build("/");
given(mockedExportFile.getExport(fsRoot.exportIndex(), privilegedClient.getAddress())).willReturn(export);
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
pseudoFs.getattr(fsRoot);
}
}
......@@ -30,3 +30,6 @@ not/absolute/path
/export_default_pnfs 192.168.1.1(rw)
/layouttypes 172.16.1.1(rw,lt=nfsv4_1_files) 172.16.2.1(rw,lt=flex_files:nfsv4_1_files) 172.16.3.1(rw,lt=nfsv4_1_files:flex_files) 172.16.4.1(rw)
/order_test 192.168.17.0/24(ro) 192.168.17.1(rw)
/secure 192.168.17.0/24(secure)
/insecure 192.168.17.0/24(insecure)
/secure_default 192.168.17.0/24
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment