Commit 1c80fb61 authored by Tigran Mkrtchyan's avatar Tigran Mkrtchyan
Browse files

vfs: introduce get/set/list/removeXattr methods

Motivation:
In order NFS server to support extended attributes, they should be exposed
by under laying vfs interface.

Modification:
introduce VirtualFileSystem#get/set/list/removeXattr methods with default
implementations. Update PseudoFs to check permissions and bypass
requests to backing implementation.

To read or list extended attributes, read permission on the file is required
To set or remove extended attributes, write permissions on the file is required

Result:
ground work for extended attribute support.

Acked-by: Lea Morschel
Acked-by: Paul Millar
Target: master
parent 2b803c0d
/*
* Copyright (c) 2009 - 2015 Deutsches Elektronen-Synchroton,
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
*
* This library is free software; you can redistribute it and/or modify
......@@ -272,5 +272,9 @@ public interface nfs4_prot {
public static final int FATTR4_LAYOUT_TYPE = 64;
public static final int FSLI4TF_RDMA = 0x01;
public static final int NFSPROC4_COMPOUND_4 = 1;
public static final int ACCESS4_XAREAD = 0x00000040;
public static final int ACCESS4_XALIST = 0x00000100;
public static final int ACCESS4_XAWRITE = 0x00000080;
}
// End of nfs4_prot.java
......@@ -137,6 +137,31 @@ public class PseudoFs extends ForwardingFileSystem {
}
}
/**
* rfc8276 specifies only 'user' attributes. Thus access to access to them is controlled
* as access to the file:
* - to read or list xattrs file read permission is required
* - to set or delete xattrs file write permission is required
*/
if ((mode & ACCESS4_XAREAD) != 0) {
if (canAccess(inode, ACE4_READ_DATA)) {
accessmask |= ACCESS4_XAREAD;
}
}
if ((mode & ACCESS4_XALIST) != 0) {
if (canAccess(inode, ACE4_READ_DATA)) {
accessmask |= ACCESS4_XALIST;
}
}
if ((mode & ACCESS4_XAWRITE) != 0) {
if (canAccess(inode, ACE4_WRITE_DATA)) {
accessmask |= ACCESS4_XAWRITE;
}
}
return accessmask & _inner.access(inode, accessmask);
}
......@@ -338,6 +363,30 @@ public class PseudoFs extends ForwardingFileSystem {
_inner.setAcl(inode, acl);
}
@Override
public byte[] getXattr(Inode inode, String attr) throws IOException {
checkAccess(inode, ACE4_READ_DATA);
return _inner.getXattr(inode, attr);
}
@Override
public void setXattr(Inode inode, String attr, byte[] value, SetXattrMode mode) throws IOException {
checkAccess(inode, ACE4_WRITE_DATA);
_inner.setXattr(inode, attr, value, mode);
}
@Override
public String[] listXattrs(Inode inode) throws IOException {
checkAccess(inode, ACE4_READ_DATA);
return _inner.listXattrs(inode);
}
@Override
public void removeXattr(Inode inode, String attr) throws IOException {
checkAccess(inode, ACE4_WRITE_DATA);
_inner.removeXattr(inode, attr);
}
private Subject checkAccess(Inode inode, int requestedMask) throws IOException {
return checkAccess(inode, requestedMask, true);
}
......
/*
* Copyright (c) 2009 - 2017 Deutsches Elektronen-Synchroton,
* Copyright (c) 2009 - 2019 Deutsches Elektronen-Synchroton,
* Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY
*
* This library is free software; you can redistribute it and/or modify
......@@ -21,6 +21,7 @@ package org.dcache.nfs.vfs;
import java.io.IOException;
import javax.security.auth.Subject;
import org.dcache.nfs.status.NotSuppException;
import org.dcache.nfs.v4.NfsIdMapping;
import org.dcache.nfs.v4.xdr.nfsace4;
import org.dcache.nfs.v4.xdr.stable_how4;
......@@ -379,4 +380,71 @@ public interface VirtualFileSystem {
return ordinal();
}
}
/**
* The modes of setXattr.
*/
public enum SetXattrMode {
/**
* Create a new extended attribute.
*/
CREATE,
/**
* Replace an existing extended attribute.
*/
REPLACE,
/**
* Create a new extended attribute or replace the value, if the attribute
* exists.
*/
EITHER
}
/**
* Get an Extended Attribute of a inode.
* @param inode file system object.
* @param attr extended attribute name.
* @return value of the attribute.
* @throws IOException
*/
default byte[] getXattr(Inode inode, String attr) throws IOException {
throw new NotSuppException();
}
/**
* Set or change extended attribute of a given file system object.
* @param inode file system object.
* @param attr extended attribute name.
* @param value of the attribute.
* @param mode the update mode
* @throws IOException
*/
default void setXattr(Inode inode, String attr, byte[] value, SetXattrMode mode) throws IOException {
throw new NotSuppException();
}
/**
* Retrieve an array of extended attribute names for a given file system object.
*
* @param inode file system object.
* @return an array of extended attribute names.
* @throws IOException
*/
default String[] listXattrs(Inode inode) throws IOException {
throw new NotSuppException();
}
/**
* Remove specified extended attribute for a given file system object.
*
* @param inode file system object.
* @param attr extended attribute name.
* @throws IOException
*/
default void removeXattr(Inode inode, String attr) throws IOException {
throw new NotSuppException();
}
}
......@@ -53,10 +53,12 @@ import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.security.Principal;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
......@@ -70,7 +72,6 @@ import org.dcache.nfs.status.PermException;
import org.dcache.nfs.status.ServerFaultException;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import java.util.EnumSet;
/**
* Stolen from https://github.com/kofemann/simple-nfs/blob/master/src/main/java/org/dcache/simplenfs/LocalFileSystem.java
......@@ -125,7 +126,7 @@ public class DummyVFS implements VirtualFileSystem {
.unix()
.toBuilder()
.setWorkingDirectory("/")
.setAttributeViews("posix", "owner")
.setAttributeViews("posix", "owner", "user")
.setDefaultAttributeValue("owner:owner", "0")
.setDefaultAttributeValue("posix:group", "0")
.setDefaultAttributeValue("posix:permissions", "rwxr-xr-x")
......@@ -582,6 +583,55 @@ public class DummyVFS implements VirtualFileSystem {
return _idMapper;
}
@Override
public byte[] getXattr(Inode inode, String attr) throws IOException {
long inodeNumber = toInodeNumber(inode);
Path path = resolveInode(inodeNumber);
UserDefinedFileAttributeView view
= Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
ByteBuffer buf = ByteBuffer.allocate(view.size(attr));
view.read(attr, buf);
return buf.array();
}
@Override
public void setXattr(Inode inode, String attr, byte[] value, SetXattrMode mode) throws IOException {
long inodeNumber = toInodeNumber(inode);
Path path = resolveInode(inodeNumber);
UserDefinedFileAttributeView view
= Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
ByteBuffer buf = ByteBuffer.wrap(value);
view.write(attr, buf);
}
@Override
public String[] listXattrs(Inode inode) throws IOException {
long inodeNumber = toInodeNumber(inode);
Path path = resolveInode(inodeNumber);
UserDefinedFileAttributeView view
= Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
return view.list().toArray(new String[0]);
}
@Override
public void removeXattr(Inode inode, String attr) throws IOException {
long inodeNumber = toInodeNumber(inode);
Path path = resolveInode(inodeNumber);
UserDefinedFileAttributeView view
= Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
view.delete(attr);
}
private static Principal asUserPrincipal(int uid) {
return new UserPrincipal() {
@Override
......
......@@ -21,6 +21,7 @@ package org.dcache.nfs.vfs;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import javax.security.auth.Subject;
import org.dcache.auth.Subjects;
......@@ -442,4 +443,258 @@ public class PseudoFsTest {
pseudoFs.mkdir(exportDir, "bar", Subjects.ROOT, 0755);
}
@Test
public void testAllowOwnerReadXattr() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.of(1, 1));
given(mockedAuth.type()).willReturn(RpcAuthType.UNIX);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
Subject subject = Subjects.of(1, 17);
Inode parent = vfs.mkdir(fsRoot, "dir", subject, 0755);
Inode file = vfs.create(parent, Stat.Type.REGULAR, "aFile", subject, 0600);
vfs.setXattr(file, "xattr1", "value1".getBytes(StandardCharsets.UTF_8),
VirtualFileSystem.SetXattrMode.CREATE);
FsExport export = new FsExport.FsExportBuilder()
.ro()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.SYS)
.build("/dir");
given(mockedExportFile.getExport(export.getIndex(), localAddress.getAddress())).willReturn(export);
given(mockedExportFile.exports(localAddress.getAddress())).willAnswer(x -> Stream.of(export));
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
Inode pseudoRoot = pseudoFs.getRootInode();
Inode dir = pseudoFs.lookup(pseudoRoot, "dir");
Inode inode = pseudoFs.lookup(dir, "aFile");
pseudoFs.getXattr(inode, "xattr1");
}
@Test(expected = AccessException.class)
public void testRejectReadXattr() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.of(1, 1));
given(mockedAuth.type()).willReturn(RpcAuthType.UNIX);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
Subject subject = Subjects.of(17, 17);
Inode parent = vfs.mkdir(fsRoot, "dir", subject, 0755);
vfs.create(parent, Stat.Type.REGULAR, "aFile", subject, 0600);
FsExport export = new FsExport.FsExportBuilder()
.ro()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.SYS)
.build("/dir");
given(mockedExportFile.getExport(export.getIndex(), localAddress.getAddress())).willReturn(export);
given(mockedExportFile.exports(localAddress.getAddress())).willAnswer(x -> Stream.of(export));
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
Inode pseudoRoot = pseudoFs.getRootInode();
Inode dir = pseudoFs.lookup(pseudoRoot, "dir");
Inode inode = pseudoFs.lookup(dir, "aFile");
pseudoFs.getXattr(inode, "xattr1");
}
@Test
public void testAllowOwnerListXattrs() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.of(1, 1));
given(mockedAuth.type()).willReturn(RpcAuthType.UNIX);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
Subject subject = Subjects.of(1, 17);
Inode parent = vfs.mkdir(fsRoot, "dir", subject, 0755);
vfs.create(parent, Stat.Type.REGULAR, "aFile", subject, 0600);
FsExport export = new FsExport.FsExportBuilder()
.ro()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.SYS)
.build("/dir");
given(mockedExportFile.getExport(export.getIndex(), localAddress.getAddress())).willReturn(export);
given(mockedExportFile.exports(localAddress.getAddress())).willAnswer(x -> Stream.of(export));
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
Inode pseudoRoot = pseudoFs.getRootInode();
Inode dir = pseudoFs.lookup(pseudoRoot, "dir");
Inode inode = pseudoFs.lookup(dir, "aFile");
pseudoFs.listXattrs(inode);
}
@Test(expected = AccessException.class)
public void testRejectListXattrs() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.of(1, 1));
given(mockedAuth.type()).willReturn(RpcAuthType.UNIX);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
Subject subject = Subjects.of(17, 17);
Inode parent = vfs.mkdir(fsRoot, "dir", subject, 0755);
vfs.create(parent, Stat.Type.REGULAR, "aFile", subject, 0600);
FsExport export = new FsExport.FsExportBuilder()
.ro()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.SYS)
.build("/dir");
given(mockedExportFile.getExport(export.getIndex(), localAddress.getAddress())).willReturn(export);
given(mockedExportFile.exports(localAddress.getAddress())).willAnswer(x -> Stream.of(export));
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
Inode pseudoRoot = pseudoFs.getRootInode();
Inode dir = pseudoFs.lookup(pseudoRoot, "dir");
Inode inode = pseudoFs.lookup(dir, "aFile");
pseudoFs.listXattrs(inode);
}
@Test
public void testAllowOwnerRemoveXattr() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.of(1, 1));
given(mockedAuth.type()).willReturn(RpcAuthType.UNIX);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
Subject subject = Subjects.of(1, 17);
Inode parent = vfs.mkdir(fsRoot, "dir", subject, 0755);
vfs.create(parent, Stat.Type.REGULAR, "aFile", subject, 0600);
FsExport export = new FsExport.FsExportBuilder()
.rw()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.SYS)
.build("/dir");
given(mockedExportFile.getExport(export.getIndex(), localAddress.getAddress())).willReturn(export);
given(mockedExportFile.exports(localAddress.getAddress())).willAnswer(x -> Stream.of(export));
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
Inode pseudoRoot = pseudoFs.getRootInode();
Inode dir = pseudoFs.lookup(pseudoRoot, "dir");
Inode inode = pseudoFs.lookup(dir, "aFile");
pseudoFs.removeXattr(inode, "xattr1");
}
@Test(expected = AccessException.class)
public void testRejectRemoveXattr() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.of(1, 1));
given(mockedAuth.type()).willReturn(RpcAuthType.UNIX);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
Subject subject = Subjects.of(17, 17);
Inode parent = vfs.mkdir(fsRoot, "dir", subject, 0755);
vfs.create(parent, Stat.Type.REGULAR, "aFile", subject, 0600);
FsExport export = new FsExport.FsExportBuilder()
.rw()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.SYS)
.build("/dir");
given(mockedExportFile.getExport(export.getIndex(), localAddress.getAddress())).willReturn(export);
given(mockedExportFile.exports(localAddress.getAddress())).willAnswer(x -> Stream.of(export));
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
Inode pseudoRoot = pseudoFs.getRootInode();
Inode dir = pseudoFs.lookup(pseudoRoot, "dir");
Inode inode = pseudoFs.lookup(dir, "aFile");
pseudoFs.removeXattr(inode, "xattr1");
}
@Test
public void testAllowOwnerWriteXattr() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.of(1, 1));
given(mockedAuth.type()).willReturn(RpcAuthType.UNIX);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
Subject subject = Subjects.of(1, 17);
Inode parent = vfs.mkdir(fsRoot, "dir", subject, 0755);
vfs.create(parent, Stat.Type.REGULAR, "aFile", subject, 0600);
FsExport export = new FsExport.FsExportBuilder()
.rw()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.SYS)
.build("/dir");
given(mockedExportFile.getExport(export.getIndex(), localAddress.getAddress())).willReturn(export);
given(mockedExportFile.exports(localAddress.getAddress())).willAnswer(x -> Stream.of(export));
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
Inode pseudoRoot = pseudoFs.getRootInode();
Inode dir = pseudoFs.lookup(pseudoRoot, "dir");
Inode inode = pseudoFs.lookup(dir, "aFile");
pseudoFs.setXattr(inode, "xattr1", "value1".getBytes(StandardCharsets.UTF_8), VirtualFileSystem.SetXattrMode.CREATE);
}
@Test(expected = AccessException.class)
public void testRejectWriteXattr() throws IOException {
given(mockedTransport.getRemoteSocketAddress()).willReturn(localAddress);
given(mockedAuth.getSubject()).willReturn(Subjects.of(1, 1));
given(mockedAuth.type()).willReturn(RpcAuthType.UNIX);
given(mockedRpc.getTransport()).willReturn(mockedTransport);
given(mockedRpc.getCredential()).willReturn(mockedAuth);
Subject subject = Subjects.of(17, 17);
Inode parent = vfs.mkdir(fsRoot, "dir", subject, 0755);
vfs.create(parent, Stat.Type.REGULAR, "aFile", subject, 0600);
FsExport export = new FsExport.FsExportBuilder()
.rw()
.trusted()
.withoutAcl()
.withSec(FsExport.Sec.SYS)
.build("/dir");
given(mockedExportFile.getExport(export.getIndex(), localAddress.getAddress())).willReturn(export);
given(mockedExportFile.exports(localAddress.getAddress())).willAnswer(x -> Stream.of(export));
pseudoFs = new PseudoFs(vfs, mockedRpc, mockedExportFile);
Inode pseudoRoot = pseudoFs.getRootInode();
Inode dir = pseudoFs.lookup(pseudoRoot, "dir");
Inode inode = pseudoFs.lookup(dir, "aFile");
pseudoFs.setXattr(inode, "xattr1", "value1".getBytes(StandardCharsets.UTF_8), VirtualFileSystem.SetXattrMode.CREATE);
}
}
Supports Markdown
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