Merge changes from trunk
This commit is contained in:
commit
911979c8ab
|
@ -815,6 +815,17 @@ Release 2.6.0 - UNRELEASED
|
||||||
HADOOP-11088. Unittest TestKeyShell, TestCredShell and TestKMS assume UNIX
|
HADOOP-11088. Unittest TestKeyShell, TestCredShell and TestKMS assume UNIX
|
||||||
path separator for JECKS key store path. (Xiaoyu Yao via cnauroth)
|
path separator for JECKS key store path. (Xiaoyu Yao via cnauroth)
|
||||||
|
|
||||||
|
HADOOP-11096. KMS: KeyAuthorizationKeyProvider should verify the keyversion
|
||||||
|
belongs to the keyname on decrypt. (tucu)
|
||||||
|
|
||||||
|
HADOOP-11097. kms docs say proxyusers, not proxyuser for config params.
|
||||||
|
(clamb via tucu)
|
||||||
|
|
||||||
|
HADOOP-11062. CryptoCodec testcases requiring OpenSSL should be run
|
||||||
|
only if -Pnative is used. (asuresh via tucu)
|
||||||
|
|
||||||
|
HADOOP-11099. KMS return HTTP UNAUTHORIZED 401 on ACL failure. (tucu)
|
||||||
|
|
||||||
Release 2.5.1 - 2014-09-05
|
Release 2.5.1 - 2014-09-05
|
||||||
|
|
||||||
INCOMPATIBLE CHANGES
|
INCOMPATIBLE CHANGES
|
||||||
|
|
|
@ -375,6 +375,7 @@
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<startKdc>${startKdc}</startKdc>
|
<startKdc>${startKdc}</startKdc>
|
||||||
<kdc.resource.dir>${kdc.resource.dir}</kdc.resource.dir>
|
<kdc.resource.dir>${kdc.resource.dir}</kdc.resource.dir>
|
||||||
|
<runningWithNative>${runningWithNative}</runningWithNative>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
<properties>
|
<properties>
|
||||||
<property>
|
<property>
|
||||||
|
@ -507,6 +508,7 @@
|
||||||
<openssl.lib></openssl.lib>
|
<openssl.lib></openssl.lib>
|
||||||
<openssl.include></openssl.include>
|
<openssl.include></openssl.include>
|
||||||
<require.openssl>false</require.openssl>
|
<require.openssl>false</require.openssl>
|
||||||
|
<runningWithNative>true</runningWithNative>
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
@ -626,6 +628,7 @@
|
||||||
<openssl.lib></openssl.lib>
|
<openssl.lib></openssl.lib>
|
||||||
<openssl.include></openssl.include>
|
<openssl.include></openssl.include>
|
||||||
<require.openssl>false</require.openssl>
|
<require.openssl>false</require.openssl>
|
||||||
|
<runningWithNative>true</runningWithNative>
|
||||||
<bundle.openssl.in.bin>true</bundle.openssl.in.bin>
|
<bundle.openssl.in.bin>true</bundle.openssl.in.bin>
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -91,6 +91,8 @@ public class KeyProviderCryptoExtension extends
|
||||||
* returned EncryptedKeyVersion will only partially be populated; it is not
|
* returned EncryptedKeyVersion will only partially be populated; it is not
|
||||||
* necessarily suitable for operations besides decryption.
|
* necessarily suitable for operations besides decryption.
|
||||||
*
|
*
|
||||||
|
* @param keyName Key name of the encryption key use to encrypt the
|
||||||
|
* encrypted key.
|
||||||
* @param encryptionKeyVersionName Version name of the encryption key used
|
* @param encryptionKeyVersionName Version name of the encryption key used
|
||||||
* to encrypt the encrypted key.
|
* to encrypt the encrypted key.
|
||||||
* @param encryptedKeyIv Initialization vector of the encrypted
|
* @param encryptedKeyIv Initialization vector of the encrypted
|
||||||
|
@ -100,12 +102,12 @@ public class KeyProviderCryptoExtension extends
|
||||||
* @param encryptedKeyMaterial Key material of the encrypted key.
|
* @param encryptedKeyMaterial Key material of the encrypted key.
|
||||||
* @return EncryptedKeyVersion suitable for decryption.
|
* @return EncryptedKeyVersion suitable for decryption.
|
||||||
*/
|
*/
|
||||||
public static EncryptedKeyVersion createForDecryption(String
|
public static EncryptedKeyVersion createForDecryption(String keyName,
|
||||||
encryptionKeyVersionName, byte[] encryptedKeyIv,
|
String encryptionKeyVersionName, byte[] encryptedKeyIv,
|
||||||
byte[] encryptedKeyMaterial) {
|
byte[] encryptedKeyMaterial) {
|
||||||
KeyVersion encryptedKeyVersion = new KeyVersion(null, EEK,
|
KeyVersion encryptedKeyVersion = new KeyVersion(null, EEK,
|
||||||
encryptedKeyMaterial);
|
encryptedKeyMaterial);
|
||||||
return new EncryptedKeyVersion(null, encryptionKeyVersionName,
|
return new EncryptedKeyVersion(keyName, encryptionKeyVersionName,
|
||||||
encryptedKeyIv, encryptedKeyVersion);
|
encryptedKeyIv, encryptedKeyVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,14 @@ public class TestCryptoCodec {
|
||||||
|
|
||||||
@Test(timeout=120000)
|
@Test(timeout=120000)
|
||||||
public void testJceAesCtrCryptoCodec() throws Exception {
|
public void testJceAesCtrCryptoCodec() throws Exception {
|
||||||
Assume.assumeTrue(NativeCodeLoader.buildSupportsOpenssl());
|
if (!"true".equalsIgnoreCase(System.getProperty("runningWithNative"))) {
|
||||||
|
LOG.warn("Skipping since test was not run with -Pnative flag");
|
||||||
|
Assume.assumeTrue(false);
|
||||||
|
}
|
||||||
|
if (!NativeCodeLoader.buildSupportsOpenssl()) {
|
||||||
|
LOG.warn("Skipping test since openSSL library not loaded");
|
||||||
|
Assume.assumeTrue(false);
|
||||||
|
}
|
||||||
Assert.assertEquals(null, OpensslCipher.getLoadingFailureReason());
|
Assert.assertEquals(null, OpensslCipher.getLoadingFailureReason());
|
||||||
cryptoCodecTest(conf, seed, 0, jceCodecClass, jceCodecClass);
|
cryptoCodecTest(conf, seed, 0, jceCodecClass, jceCodecClass);
|
||||||
cryptoCodecTest(conf, seed, count, jceCodecClass, jceCodecClass);
|
cryptoCodecTest(conf, seed, count, jceCodecClass, jceCodecClass);
|
||||||
|
@ -68,7 +75,14 @@ public class TestCryptoCodec {
|
||||||
|
|
||||||
@Test(timeout=120000)
|
@Test(timeout=120000)
|
||||||
public void testOpensslAesCtrCryptoCodec() throws Exception {
|
public void testOpensslAesCtrCryptoCodec() throws Exception {
|
||||||
Assume.assumeTrue(NativeCodeLoader.buildSupportsOpenssl());
|
if (!"true".equalsIgnoreCase(System.getProperty("runningWithNative"))) {
|
||||||
|
LOG.warn("Skipping since test was not run with -Pnative flag");
|
||||||
|
Assume.assumeTrue(false);
|
||||||
|
}
|
||||||
|
if (!NativeCodeLoader.buildSupportsOpenssl()) {
|
||||||
|
LOG.warn("Skipping test since openSSL library not loaded");
|
||||||
|
Assume.assumeTrue(false);
|
||||||
|
}
|
||||||
Assert.assertEquals(null, OpensslCipher.getLoadingFailureReason());
|
Assert.assertEquals(null, OpensslCipher.getLoadingFailureReason());
|
||||||
cryptoCodecTest(conf, seed, 0, opensslCodecClass, opensslCodecClass);
|
cryptoCodecTest(conf, seed, 0, opensslCodecClass, opensslCodecClass);
|
||||||
cryptoCodecTest(conf, seed, count, opensslCodecClass, opensslCodecClass);
|
cryptoCodecTest(conf, seed, count, opensslCodecClass, opensslCodecClass);
|
||||||
|
|
|
@ -121,7 +121,7 @@ public class TestKeyProviderCryptoExtension {
|
||||||
|
|
||||||
// Test the createForDecryption factory method
|
// Test the createForDecryption factory method
|
||||||
EncryptedKeyVersion eek2 =
|
EncryptedKeyVersion eek2 =
|
||||||
EncryptedKeyVersion.createForDecryption(
|
EncryptedKeyVersion.createForDecryption(eek.getEncryptionKeyName(),
|
||||||
eek.getEncryptionKeyVersionName(), eek.getEncryptedKeyIv(),
|
eek.getEncryptionKeyVersionName(), eek.getEncryptedKeyIv(),
|
||||||
eek.getEncryptedKeyVersion().getMaterial());
|
eek.getEncryptedKeyVersion().getMaterial());
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class KMSExceptionsProvider implements ExceptionMapper<Exception> {
|
||||||
// we don't audit here because we did it already when checking access
|
// we don't audit here because we did it already when checking access
|
||||||
doAudit = false;
|
doAudit = false;
|
||||||
} else if (throwable instanceof AuthorizationException) {
|
} else if (throwable instanceof AuthorizationException) {
|
||||||
status = Response.Status.UNAUTHORIZED;
|
status = Response.Status.FORBIDDEN;
|
||||||
// we don't audit here because we did it already when checking access
|
// we don't audit here because we did it already when checking access
|
||||||
doAudit = false;
|
doAudit = false;
|
||||||
} else if (throwable instanceof AccessControlException) {
|
} else if (throwable instanceof AccessControlException) {
|
||||||
|
|
|
@ -192,9 +192,21 @@ public class KeyAuthorizationKeyProvider extends KeyProviderCryptoExtension {
|
||||||
return provider.generateEncryptedKey(encryptionKeyName);
|
return provider.generateEncryptedKey(encryptionKeyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifyKeyVersionBelongsToKey(EncryptedKeyVersion ekv)
|
||||||
|
throws IOException {
|
||||||
|
String kn = ekv.getEncryptionKeyName();
|
||||||
|
String kvn = ekv.getEncryptionKeyVersionName();
|
||||||
|
KeyVersion kv = provider.getKeyVersion(kvn);
|
||||||
|
if (!kv.getName().equals(kn)) {
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"KeyVersion '%s' does not belong to the key '%s'", kvn, kn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyVersion decryptEncryptedKey(EncryptedKeyVersion encryptedKeyVersion)
|
public KeyVersion decryptEncryptedKey(EncryptedKeyVersion encryptedKeyVersion)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
|
verifyKeyVersionBelongsToKey(encryptedKeyVersion);
|
||||||
doAccessCheck(
|
doAccessCheck(
|
||||||
encryptedKeyVersion.getEncryptionKeyName(), KeyOpType.DECRYPT_EEK);
|
encryptedKeyVersion.getEncryptionKeyName(), KeyOpType.DECRYPT_EEK);
|
||||||
return provider.decryptEncryptedKey(encryptedKeyVersion);
|
return provider.decryptEncryptedKey(encryptedKeyVersion);
|
||||||
|
|
|
@ -197,22 +197,22 @@ hadoop-${project.version} $ sbin/kms.sh start
|
||||||
|
|
||||||
*** KMS Proxyuser Configuration
|
*** KMS Proxyuser Configuration
|
||||||
|
|
||||||
Each proxyusers must be configured in <<<etc/hadoop/kms-site.xml>>> using the
|
Each proxyuser must be configured in <<<etc/hadoop/kms-site.xml>>> using the
|
||||||
following properties:
|
following properties:
|
||||||
|
|
||||||
+---+
|
+---+
|
||||||
<property>
|
<property>
|
||||||
<name>hadoop.kms.proxyusers.#USER#.users</name>
|
<name>hadoop.kms.proxyuser.#USER#.users</name>
|
||||||
<value>*</value>
|
<value>*</value>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<property>
|
<property>
|
||||||
<name>hadoop.kms.proxyusers.#USER#.groups</name>
|
<name>hadoop.kms.proxyuser.#USER#.groups</name>
|
||||||
<value>*</value>
|
<value>*</value>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<property>
|
<property>
|
||||||
<name>hadoop.kms.proxyusers.#USER#.hosts</name>
|
<name>hadoop.kms.proxyuser.#USER#.hosts</name>
|
||||||
<value>*</value>
|
<value>*</value>
|
||||||
</property>
|
</property>
|
||||||
+---+
|
+---+
|
||||||
|
|
|
@ -215,4 +215,57 @@ public class TestKeyAuthorizationKeyProvider {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testDecryptWithKeyVersionNameKeyMismatch() throws Exception {
|
||||||
|
final Configuration conf = new Configuration();
|
||||||
|
KeyProvider kp =
|
||||||
|
new UserProvider.Factory().createProvider(new URI("user:///"), conf);
|
||||||
|
KeyACLs mock = mock(KeyACLs.class);
|
||||||
|
when(mock.isACLPresent("testKey", KeyOpType.MANAGEMENT)).thenReturn(true);
|
||||||
|
when(mock.isACLPresent("testKey", KeyOpType.GENERATE_EEK)).thenReturn(true);
|
||||||
|
when(mock.isACLPresent("testKey", KeyOpType.DECRYPT_EEK)).thenReturn(true);
|
||||||
|
when(mock.isACLPresent("testKey", KeyOpType.ALL)).thenReturn(true);
|
||||||
|
UserGroupInformation u1 = UserGroupInformation.createRemoteUser("u1");
|
||||||
|
UserGroupInformation u2 = UserGroupInformation.createRemoteUser("u2");
|
||||||
|
UserGroupInformation u3 = UserGroupInformation.createRemoteUser("u3");
|
||||||
|
UserGroupInformation sudo = UserGroupInformation.createRemoteUser("sudo");
|
||||||
|
when(mock.hasAccessToKey("testKey", u1,
|
||||||
|
KeyOpType.MANAGEMENT)).thenReturn(true);
|
||||||
|
when(mock.hasAccessToKey("testKey", u2,
|
||||||
|
KeyOpType.GENERATE_EEK)).thenReturn(true);
|
||||||
|
when(mock.hasAccessToKey("testKey", u3,
|
||||||
|
KeyOpType.DECRYPT_EEK)).thenReturn(true);
|
||||||
|
when(mock.hasAccessToKey("testKey", sudo,
|
||||||
|
KeyOpType.ALL)).thenReturn(true);
|
||||||
|
final KeyProviderCryptoExtension kpExt =
|
||||||
|
new KeyAuthorizationKeyProvider(
|
||||||
|
KeyProviderCryptoExtension.createKeyProviderCryptoExtension(kp),
|
||||||
|
mock);
|
||||||
|
|
||||||
|
sudo.doAs(
|
||||||
|
new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
Options opt = newOptions(conf);
|
||||||
|
Map<String, String> m = new HashMap<String, String>();
|
||||||
|
m.put("key.acl.name", "testKey");
|
||||||
|
opt.setAttributes(m);
|
||||||
|
KeyVersion kv =
|
||||||
|
kpExt.createKey("foo", SecureRandom.getSeed(16), opt);
|
||||||
|
kpExt.rollNewVersion(kv.getName());
|
||||||
|
kpExt.rollNewVersion(kv.getName(), SecureRandom.getSeed(16));
|
||||||
|
EncryptedKeyVersion ekv = kpExt.generateEncryptedKey(kv.getName());
|
||||||
|
ekv = EncryptedKeyVersion.createForDecryption(
|
||||||
|
ekv.getEncryptionKeyName() + "x",
|
||||||
|
ekv.getEncryptionKeyVersionName(),
|
||||||
|
ekv.getEncryptedKeyIv(),
|
||||||
|
ekv.getEncryptedKeyVersion().getMaterial());
|
||||||
|
kpExt.decryptEncryptedKey(ekv);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -541,6 +541,9 @@ Release 2.6.0 - UNRELEASED
|
||||||
|
|
||||||
HDFS-6851. Refactor EncryptionZoneWithId and EncryptionZone. (clamb via wang)
|
HDFS-6851. Refactor EncryptionZoneWithId and EncryptionZone. (clamb via wang)
|
||||||
|
|
||||||
|
HDFS-6705. Create an XAttr that disallows the HDFS admin from accessing a
|
||||||
|
file. (clamb via wang)
|
||||||
|
|
||||||
OPTIMIZATIONS
|
OPTIMIZATIONS
|
||||||
|
|
||||||
HDFS-6690. Deduplicate xattr names in memory. (wang)
|
HDFS-6690. Deduplicate xattr names in memory. (wang)
|
||||||
|
|
|
@ -209,6 +209,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<startKdc>${startKdc}</startKdc>
|
<startKdc>${startKdc}</startKdc>
|
||||||
<kdc.resource.dir>${kdc.resource.dir}</kdc.resource.dir>
|
<kdc.resource.dir>${kdc.resource.dir}</kdc.resource.dir>
|
||||||
|
<runningWithNative>${runningWithNative}</runningWithNative>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
<properties>
|
<properties>
|
||||||
<property>
|
<property>
|
||||||
|
@ -386,6 +387,9 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<family>windows</family>
|
<family>windows</family>
|
||||||
</os>
|
</os>
|
||||||
</activation>
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<runningWithNative>true</runningWithNative>
|
||||||
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
@ -476,6 +480,9 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<activation>
|
<activation>
|
||||||
<activeByDefault>false</activeByDefault>
|
<activeByDefault>false</activeByDefault>
|
||||||
</activation>
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<runningWithNative>true</runningWithNative>
|
||||||
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|
|
@ -1321,7 +1321,8 @@ public class DFSClient implements java.io.Closeable, RemotePeerFactory,
|
||||||
" an encrypted file");
|
" an encrypted file");
|
||||||
}
|
}
|
||||||
EncryptedKeyVersion ekv = EncryptedKeyVersion.createForDecryption(
|
EncryptedKeyVersion ekv = EncryptedKeyVersion.createForDecryption(
|
||||||
feInfo.getEzKeyVersionName(), feInfo.getIV(),
|
//TODO: here we have to put the keyName to be provided by HDFS-6987
|
||||||
|
null, feInfo.getEzKeyVersionName(), feInfo.getIV(),
|
||||||
feInfo.getEncryptedDataEncryptionKey());
|
feInfo.getEncryptedDataEncryptionKey());
|
||||||
try {
|
try {
|
||||||
return provider.decryptEncryptedKey(ekv);
|
return provider.decryptEncryptedKey(ekv);
|
||||||
|
|
|
@ -299,5 +299,6 @@ public final class HdfsServerConstants {
|
||||||
"raw.hdfs.crypto.encryption.zone";
|
"raw.hdfs.crypto.encryption.zone";
|
||||||
public static final String CRYPTO_XATTR_FILE_ENCRYPTION_INFO =
|
public static final String CRYPTO_XATTR_FILE_ENCRYPTION_INFO =
|
||||||
"raw.hdfs.crypto.file.encryption.info";
|
"raw.hdfs.crypto.file.encryption.info";
|
||||||
|
public static final String SECURITY_XATTR_UNREADABLE_BY_SUPERUSER =
|
||||||
|
"security.hdfs.unreadable.by.superuser";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.apache.hadoop.hdfs.server.namenode;
|
||||||
import static org.apache.hadoop.fs.BatchedRemoteIterator.BatchedListEntries;
|
import static org.apache.hadoop.fs.BatchedRemoteIterator.BatchedListEntries;
|
||||||
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.CRYPTO_XATTR_ENCRYPTION_ZONE;
|
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.CRYPTO_XATTR_ENCRYPTION_ZONE;
|
||||||
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.CRYPTO_XATTR_FILE_ENCRYPTION_INFO;
|
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.CRYPTO_XATTR_FILE_ENCRYPTION_INFO;
|
||||||
|
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.SECURITY_XATTR_UNREADABLE_BY_SUPERUSER;
|
||||||
import static org.apache.hadoop.util.Time.now;
|
import static org.apache.hadoop.util.Time.now;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
@ -91,6 +92,7 @@ import org.apache.hadoop.hdfs.util.ReadOnlyList;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import org.apache.hadoop.security.AccessControlException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Both FSDirectory and FSNamesystem manage the state of the namespace.
|
* Both FSDirectory and FSNamesystem manage the state of the namespace.
|
||||||
|
@ -129,6 +131,8 @@ public class FSDirectory implements Closeable {
|
||||||
DFSUtil.string2Bytes(DOT_INODES_STRING);
|
DFSUtil.string2Bytes(DOT_INODES_STRING);
|
||||||
private final XAttr KEYID_XATTR =
|
private final XAttr KEYID_XATTR =
|
||||||
XAttrHelper.buildXAttr(CRYPTO_XATTR_ENCRYPTION_ZONE, null);
|
XAttrHelper.buildXAttr(CRYPTO_XATTR_ENCRYPTION_ZONE, null);
|
||||||
|
private final XAttr UNREADABLE_BY_SUPERUSER_XATTR =
|
||||||
|
XAttrHelper.buildXAttr(SECURITY_XATTR_UNREADABLE_BY_SUPERUSER, null);
|
||||||
|
|
||||||
INodeDirectory rootDir;
|
INodeDirectory rootDir;
|
||||||
private final FSNamesystem namesystem;
|
private final FSNamesystem namesystem;
|
||||||
|
@ -2670,7 +2674,8 @@ public class FSDirectory implements Closeable {
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
List<XAttr> filterINodeXAttrs(final List<XAttr> existingXAttrs,
|
List<XAttr> filterINodeXAttrs(final List<XAttr> existingXAttrs,
|
||||||
final List<XAttr> toFilter, final List<XAttr> filtered) {
|
final List<XAttr> toFilter, final List<XAttr> filtered)
|
||||||
|
throws AccessControlException {
|
||||||
if (existingXAttrs == null || existingXAttrs.isEmpty() ||
|
if (existingXAttrs == null || existingXAttrs.isEmpty() ||
|
||||||
toFilter == null || toFilter.isEmpty()) {
|
toFilter == null || toFilter.isEmpty()) {
|
||||||
return existingXAttrs;
|
return existingXAttrs;
|
||||||
|
@ -2686,6 +2691,10 @@ public class FSDirectory implements Closeable {
|
||||||
XAttr filter = it.next();
|
XAttr filter = it.next();
|
||||||
Preconditions.checkArgument(!KEYID_XATTR.equalsIgnoreValue(filter),
|
Preconditions.checkArgument(!KEYID_XATTR.equalsIgnoreValue(filter),
|
||||||
"The encryption zone xattr should never be deleted.");
|
"The encryption zone xattr should never be deleted.");
|
||||||
|
if (UNREADABLE_BY_SUPERUSER_XATTR.equalsIgnoreValue(filter)) {
|
||||||
|
throw new AccessControlException("The xattr '" +
|
||||||
|
SECURITY_XATTR_UNREADABLE_BY_SUPERUSER + "' can not be deleted.");
|
||||||
|
}
|
||||||
if (a.equalsIgnoreValue(filter)) {
|
if (a.equalsIgnoreValue(filter)) {
|
||||||
add = false;
|
add = false;
|
||||||
it.remove();
|
it.remove();
|
||||||
|
@ -2824,16 +2833,23 @@ public class FSDirectory implements Closeable {
|
||||||
int snapshotId = iip.getLatestSnapshotId();
|
int snapshotId = iip.getLatestSnapshotId();
|
||||||
List<XAttr> existingXAttrs = XAttrStorage.readINodeXAttrs(inode);
|
List<XAttr> existingXAttrs = XAttrStorage.readINodeXAttrs(inode);
|
||||||
List<XAttr> newXAttrs = setINodeXAttrs(existingXAttrs, xAttrs, flag);
|
List<XAttr> newXAttrs = setINodeXAttrs(existingXAttrs, xAttrs, flag);
|
||||||
|
final boolean isFile = inode.isFile();
|
||||||
|
|
||||||
/*
|
|
||||||
* If we're adding the encryption zone xattr, then add src to the list
|
|
||||||
* of encryption zones.
|
|
||||||
*/
|
|
||||||
for (XAttr xattr : newXAttrs) {
|
for (XAttr xattr : newXAttrs) {
|
||||||
final String xaName = XAttrHelper.getPrefixName(xattr);
|
final String xaName = XAttrHelper.getPrefixName(xattr);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we're adding the encryption zone xattr, then add src to the list
|
||||||
|
* of encryption zones.
|
||||||
|
*/
|
||||||
if (CRYPTO_XATTR_ENCRYPTION_ZONE.equals(xaName)) {
|
if (CRYPTO_XATTR_ENCRYPTION_ZONE.equals(xaName)) {
|
||||||
ezManager.addEncryptionZone(inode.getId(), new String(xattr.getValue()));
|
ezManager.addEncryptionZone(inode.getId(), new String(xattr.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isFile && SECURITY_XATTR_UNREADABLE_BY_SUPERUSER.equals(xaName)) {
|
||||||
|
throw new IOException("Can only set '" +
|
||||||
|
SECURITY_XATTR_UNREADABLE_BY_SUPERUSER + "' on a file.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
XAttrStorage.updateINodeXAttrs(inode, newXAttrs, snapshotId);
|
XAttrStorage.updateINodeXAttrs(inode, newXAttrs, snapshotId);
|
||||||
|
@ -2924,12 +2940,26 @@ public class FSDirectory implements Closeable {
|
||||||
INodesInPath iip = getLastINodeInPath(srcs, true);
|
INodesInPath iip = getLastINodeInPath(srcs, true);
|
||||||
INode inode = resolveLastINode(src, iip);
|
INode inode = resolveLastINode(src, iip);
|
||||||
int snapshotId = iip.getPathSnapshotId();
|
int snapshotId = iip.getPathSnapshotId();
|
||||||
return XAttrStorage.readINodeXAttrs(inode, snapshotId);
|
return unprotectedGetXAttrs(inode, snapshotId);
|
||||||
} finally {
|
} finally {
|
||||||
readUnlock();
|
readUnlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<XAttr> getXAttrs(INode inode, int snapshotId) throws IOException {
|
||||||
|
readLock();
|
||||||
|
try {
|
||||||
|
return unprotectedGetXAttrs(inode, snapshotId);
|
||||||
|
} finally {
|
||||||
|
readUnlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<XAttr> unprotectedGetXAttrs(INode inode, int snapshotId)
|
||||||
|
throws IOException {
|
||||||
|
return XAttrStorage.readINodeXAttrs(inode, snapshotId);
|
||||||
|
}
|
||||||
|
|
||||||
private static INode resolveLastINode(String src, INodesInPath iip)
|
private static INode resolveLastINode(String src, INodesInPath iip)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
INode inode = iip.getLastINode();
|
INode inode = iip.getLastINode();
|
||||||
|
|
|
@ -88,6 +88,7 @@ import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROU
|
||||||
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_KEY;
|
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_KEY;
|
||||||
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_REPLICATION_DEFAULT;
|
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_REPLICATION_DEFAULT;
|
||||||
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_REPLICATION_KEY;
|
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_REPLICATION_KEY;
|
||||||
|
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.SECURITY_XATTR_UNREADABLE_BY_SUPERUSER;
|
||||||
import static org.apache.hadoop.util.Time.now;
|
import static org.apache.hadoop.util.Time.now;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
|
@ -166,6 +167,8 @@ import org.apache.hadoop.hdfs.DFSConfigKeys;
|
||||||
import org.apache.hadoop.hdfs.DFSUtil;
|
import org.apache.hadoop.hdfs.DFSUtil;
|
||||||
import org.apache.hadoop.hdfs.HAUtil;
|
import org.apache.hadoop.hdfs.HAUtil;
|
||||||
import org.apache.hadoop.hdfs.HdfsConfiguration;
|
import org.apache.hadoop.hdfs.HdfsConfiguration;
|
||||||
|
import org.apache.hadoop.hdfs.StorageType;
|
||||||
|
import org.apache.hadoop.hdfs.XAttrHelper;
|
||||||
import org.apache.hadoop.hdfs.UnknownCipherSuiteException;
|
import org.apache.hadoop.hdfs.UnknownCipherSuiteException;
|
||||||
import org.apache.hadoop.hdfs.protocol.AclException;
|
import org.apache.hadoop.hdfs.protocol.AclException;
|
||||||
import org.apache.hadoop.hdfs.protocol.AlreadyBeingCreatedException;
|
import org.apache.hadoop.hdfs.protocol.AlreadyBeingCreatedException;
|
||||||
|
@ -1839,6 +1842,9 @@ public class FSNamesystem implements Namesystem, FSClusterStats,
|
||||||
|
|
||||||
final INodesInPath iip = dir.getLastINodeInPath(src);
|
final INodesInPath iip = dir.getLastINodeInPath(src);
|
||||||
final INodeFile inode = INodeFile.valueOf(iip.getLastINode(), src);
|
final INodeFile inode = INodeFile.valueOf(iip.getLastINode(), src);
|
||||||
|
if (isPermissionEnabled) {
|
||||||
|
checkUnreadableBySuperuser(pc, inode, iip.getPathSnapshotId());
|
||||||
|
}
|
||||||
if (!iip.isSnapshot() //snapshots are readonly, so don't update atime.
|
if (!iip.isSnapshot() //snapshots are readonly, so don't update atime.
|
||||||
&& doAccessTime && isAccessTimeSupported()) {
|
&& doAccessTime && isAccessTimeSupported()) {
|
||||||
final long now = now();
|
final long now = now();
|
||||||
|
@ -6195,6 +6201,21 @@ public class FSNamesystem implements Namesystem, FSClusterStats,
|
||||||
checkPermission(pc, path, false, null, null, access, null);
|
checkPermission(pc, path, false, null, null, access, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkUnreadableBySuperuser(FSPermissionChecker pc,
|
||||||
|
INode inode, int snapshotId)
|
||||||
|
throws IOException {
|
||||||
|
for (XAttr xattr : dir.getXAttrs(inode, snapshotId)) {
|
||||||
|
if (XAttrHelper.getPrefixName(xattr).
|
||||||
|
equals(SECURITY_XATTR_UNREADABLE_BY_SUPERUSER)) {
|
||||||
|
if (pc.isSuperUser()) {
|
||||||
|
throw new AccessControlException("Access is denied for " +
|
||||||
|
pc.getUser() + " since the superuser is not allowed to " +
|
||||||
|
"perform this operation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void checkParentAccess(FSPermissionChecker pc,
|
private void checkParentAccess(FSPermissionChecker pc,
|
||||||
String path, FsAction access) throws AccessControlException,
|
String path, FsAction access) throws AccessControlException,
|
||||||
UnresolvedLinkException {
|
UnresolvedLinkException {
|
||||||
|
@ -8958,7 +8979,9 @@ public class FSNamesystem implements Namesystem, FSClusterStats,
|
||||||
AccessControlException {
|
AccessControlException {
|
||||||
if (isPermissionEnabled && xAttr.getNameSpace() == XAttr.NameSpace.USER) {
|
if (isPermissionEnabled && xAttr.getNameSpace() == XAttr.NameSpace.USER) {
|
||||||
final INode inode = dir.getINode(src);
|
final INode inode = dir.getINode(src);
|
||||||
if (inode.isDirectory() && inode.getFsPermission().getStickyBit()) {
|
if (inode != null &&
|
||||||
|
inode.isDirectory() &&
|
||||||
|
inode.getFsPermission().getStickyBit()) {
|
||||||
if (!pc.isSuperUser()) {
|
if (!pc.isSuperUser()) {
|
||||||
checkOwner(pc, src);
|
checkOwner(pc, src);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ import org.apache.hadoop.security.AccessControlException;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.SECURITY_XATTR_UNREADABLE_BY_SUPERUSER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There are four types of extended attributes <XAttr> defined by the
|
* There are four types of extended attributes <XAttr> defined by the
|
||||||
* following namespaces:
|
* following namespaces:
|
||||||
|
@ -70,6 +72,15 @@ public class XAttrPermissionFilter {
|
||||||
isRawPath && isSuperUser) {
|
isRawPath && isSuperUser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (XAttrHelper.getPrefixName(xAttr).
|
||||||
|
equals(SECURITY_XATTR_UNREADABLE_BY_SUPERUSER)) {
|
||||||
|
if (xAttr.getValue() != null) {
|
||||||
|
throw new AccessControlException("Attempt to set a value for '" +
|
||||||
|
SECURITY_XATTR_UNREADABLE_BY_SUPERUSER +
|
||||||
|
"'. Values are not allowed for this xattr.");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw new AccessControlException("User doesn't have permission for xattr: "
|
throw new AccessControlException("User doesn't have permission for xattr: "
|
||||||
+ XAttrHelper.getPrefixName(xAttr));
|
+ XAttrHelper.getPrefixName(xAttr));
|
||||||
}
|
}
|
||||||
|
@ -104,6 +115,9 @@ public class XAttrPermissionFilter {
|
||||||
} else if (xAttr.getNameSpace() == XAttr.NameSpace.RAW &&
|
} else if (xAttr.getNameSpace() == XAttr.NameSpace.RAW &&
|
||||||
isSuperUser && isRawPath) {
|
isSuperUser && isRawPath) {
|
||||||
filteredXAttrs.add(xAttr);
|
filteredXAttrs.add(xAttr);
|
||||||
|
} else if (XAttrHelper.getPrefixName(xAttr).
|
||||||
|
equals(SECURITY_XATTR_UNREADABLE_BY_SUPERUSER)) {
|
||||||
|
filteredXAttrs.add(xAttr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ Extended Attributes in HDFS
|
||||||
|
|
||||||
The <<<system>>> namespace is reserved for internal HDFS use. This namespace is not accessible through userspace methods, and is reserved for implementing internal HDFS features.
|
The <<<system>>> namespace is reserved for internal HDFS use. This namespace is not accessible through userspace methods, and is reserved for implementing internal HDFS features.
|
||||||
|
|
||||||
The <<<security>>> namespace is reserved for internal HDFS use. This namespace is not accessible through userspace methods. It is currently unused.
|
The <<<security>>> namespace is reserved for internal HDFS use. This namespace is generally not accessible through userspace methods. One particular use of <<<security>>> is the <<<security.hdfs.unreadable.by.superuser>>> extended attribute. This xattr can only be set on files, and it will prevent the superuser from reading the file's contents. The superuser can still read and modify file metadata, such as the owner, permissions, etc. This xattr can be set and accessed by any user, assuming normal filesystem permissions. This xattr is also write-once, and cannot be removed once set. This xattr does not allow a value to be set.
|
||||||
|
|
||||||
The <<<raw>>> namespace is reserved for internal system attributes that sometimes need to be exposed. Like <<<system>>> namespace attributes they are not visible to the user except when <<<getXAttr>>>/<<<getXAttrs>>> is called on a file or directory in the <<</.reserved/raw>>> HDFS directory hierarchy. These attributes can only be accessed by the superuser. An example of where <<<raw>>> namespace extended attributes are used is the <<<distcp>>> utility. Encryption zone meta data is stored in <<<raw.*>>> extended attributes, so as long as the administrator uses <<</.reserved/raw>>> pathnames in source and target, the encrypted files in the encryption zones are transparently copied.
|
The <<<raw>>> namespace is reserved for internal system attributes that sometimes need to be exposed. Like <<<system>>> namespace attributes they are not visible to the user except when <<<getXAttr>>>/<<<getXAttrs>>> is called on a file or directory in the <<</.reserved/raw>>> HDFS directory hierarchy. These attributes can only be accessed by the superuser. An example of where <<<raw>>> namespace extended attributes are used is the <<<distcp>>> utility. Encryption zone meta data is stored in <<<raw.*>>> extended attributes, so as long as the administrator uses <<</.reserved/raw>>> pathnames in source and target, the encrypted files in the encryption zones are transparently copied.
|
||||||
|
|
||||||
|
@ -82,7 +82,6 @@ Extended Attributes in HDFS
|
||||||
|
|
||||||
* {Configuration options}
|
* {Configuration options}
|
||||||
|
|
||||||
|
|
||||||
HDFS supports extended attributes out of the box, without additional configuration. Administrators could potentially be interested in the options limiting the number of xattrs per inode and the size of xattrs, since xattrs increase the on-disk and in-memory space consumption of an inode.
|
HDFS supports extended attributes out of the box, without additional configuration. Administrators could potentially be interested in the options limiting the number of xattrs per inode and the size of xattrs, since xattrs increase the on-disk and in-memory space consumption of an inode.
|
||||||
|
|
||||||
* <<<dfs.namenode.xattrs.enabled>>>
|
* <<<dfs.namenode.xattrs.enabled>>>
|
||||||
|
|
|
@ -45,7 +45,9 @@ import static org.apache.hadoop.fs.permission.AclEntryType.USER;
|
||||||
import static org.apache.hadoop.fs.permission.FsAction.ALL;
|
import static org.apache.hadoop.fs.permission.FsAction.ALL;
|
||||||
import static org.apache.hadoop.fs.permission.FsAction.READ;
|
import static org.apache.hadoop.fs.permission.FsAction.READ;
|
||||||
import static org.apache.hadoop.hdfs.server.namenode.AclTestHelpers.aclEntry;
|
import static org.apache.hadoop.hdfs.server.namenode.AclTestHelpers.aclEntry;
|
||||||
|
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants.SECURITY_XATTR_UNREADABLE_BY_SUPERUSER;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
@ -64,13 +66,13 @@ import com.google.common.collect.Maps;
|
||||||
*/
|
*/
|
||||||
public class FSXAttrBaseTest {
|
public class FSXAttrBaseTest {
|
||||||
|
|
||||||
private static final int MAX_SIZE = 16;
|
|
||||||
|
|
||||||
protected static MiniDFSCluster dfsCluster;
|
protected static MiniDFSCluster dfsCluster;
|
||||||
protected static Configuration conf;
|
protected static Configuration conf;
|
||||||
private static int pathCount = 0;
|
private static int pathCount = 0;
|
||||||
protected static Path path;
|
protected static Path path;
|
||||||
|
protected static Path filePath;
|
||||||
protected static Path rawPath;
|
protected static Path rawPath;
|
||||||
|
protected static Path rawFilePath;
|
||||||
|
|
||||||
// XAttrs
|
// XAttrs
|
||||||
protected static final String name1 = "user.a1";
|
protected static final String name1 = "user.a1";
|
||||||
|
@ -82,6 +84,10 @@ public class FSXAttrBaseTest {
|
||||||
protected static final String name4 = "user.a4";
|
protected static final String name4 = "user.a4";
|
||||||
protected static final String raw1 = "raw.a1";
|
protected static final String raw1 = "raw.a1";
|
||||||
protected static final String raw2 = "raw.a2";
|
protected static final String raw2 = "raw.a2";
|
||||||
|
protected static final String security1 =
|
||||||
|
SECURITY_XATTR_UNREADABLE_BY_SUPERUSER;
|
||||||
|
|
||||||
|
private static final int MAX_SIZE = security1.length();
|
||||||
|
|
||||||
protected FileSystem fs;
|
protected FileSystem fs;
|
||||||
|
|
||||||
|
@ -111,7 +117,9 @@ public class FSXAttrBaseTest {
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
pathCount += 1;
|
pathCount += 1;
|
||||||
path = new Path("/p" + pathCount);
|
path = new Path("/p" + pathCount);
|
||||||
|
filePath = new Path(path, "file");
|
||||||
rawPath = new Path("/.reserved/raw/p" + pathCount);
|
rawPath = new Path("/.reserved/raw/p" + pathCount);
|
||||||
|
rawFilePath = new Path(rawPath, "file");
|
||||||
initFileSystem();
|
initFileSystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,14 +141,15 @@ public class FSXAttrBaseTest {
|
||||||
Map<String, byte[]> expectedXAttrs = Maps.newHashMap();
|
Map<String, byte[]> expectedXAttrs = Maps.newHashMap();
|
||||||
expectedXAttrs.put(name1, value1);
|
expectedXAttrs.put(name1, value1);
|
||||||
expectedXAttrs.put(name2, null);
|
expectedXAttrs.put(name2, null);
|
||||||
doTestCreateXAttr(path, expectedXAttrs);
|
expectedXAttrs.put(security1, null);
|
||||||
|
doTestCreateXAttr(filePath, expectedXAttrs);
|
||||||
expectedXAttrs.put(raw1, value1);
|
expectedXAttrs.put(raw1, value1);
|
||||||
doTestCreateXAttr(rawPath, expectedXAttrs);
|
doTestCreateXAttr(rawFilePath, expectedXAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doTestCreateXAttr(Path usePath, Map<String,
|
private void doTestCreateXAttr(Path usePath, Map<String,
|
||||||
byte[]> expectedXAttrs) throws Exception {
|
byte[]> expectedXAttrs) throws Exception {
|
||||||
FileSystem.mkdirs(fs, usePath, FsPermission.createImmutable((short)0750));
|
DFSTestUtil.createFile(fs, usePath, 8192, (short) 1, 0xFEED);
|
||||||
fs.setXAttr(usePath, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
|
fs.setXAttr(usePath, name1, value1, EnumSet.of(XAttrSetFlag.CREATE));
|
||||||
|
|
||||||
Map<String, byte[]> xattrs = fs.getXAttrs(usePath);
|
Map<String, byte[]> xattrs = fs.getXAttrs(usePath);
|
||||||
|
@ -194,9 +203,7 @@ public class FSXAttrBaseTest {
|
||||||
Assert.assertArrayEquals(val, xattrs.get(ent.getKey()));
|
Assert.assertArrayEquals(val, xattrs.get(ent.getKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, byte[]> ent : expectedXAttrs.entrySet()) {
|
fs.delete(usePath, false);
|
||||||
fs.removeXAttr(usePath, ent.getKey());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -344,13 +351,13 @@ public class FSXAttrBaseTest {
|
||||||
fs.removeXAttr(path, name3);
|
fs.removeXAttr(path, name3);
|
||||||
|
|
||||||
// Name length exceeds max limit
|
// Name length exceeds max limit
|
||||||
String longName = "user.0123456789abcdefX";
|
String longName = "user.0123456789abcdefX0123456789abcdefX0123456789abcdef";
|
||||||
try {
|
try {
|
||||||
fs.setXAttr(path, longName, null);
|
fs.setXAttr(path, longName, null);
|
||||||
Assert.fail("Setting xattr should fail if name is too long.");
|
Assert.fail("Setting xattr should fail if name is too long.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
GenericTestUtils.assertExceptionContains("XAttr is too big", e);
|
GenericTestUtils.assertExceptionContains("XAttr is too big", e);
|
||||||
GenericTestUtils.assertExceptionContains("total size is 17", e);
|
GenericTestUtils.assertExceptionContains("total size is 50", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value length exceeds max limit
|
// Value length exceeds max limit
|
||||||
|
@ -360,7 +367,7 @@ public class FSXAttrBaseTest {
|
||||||
Assert.fail("Setting xattr should fail if value is too long.");
|
Assert.fail("Setting xattr should fail if value is too long.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
GenericTestUtils.assertExceptionContains("XAttr is too big", e);
|
GenericTestUtils.assertExceptionContains("XAttr is too big", e);
|
||||||
GenericTestUtils.assertExceptionContains("total size is 17", e);
|
GenericTestUtils.assertExceptionContains("total size is 38", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name + value exactly equal the limit
|
// Name + value exactly equal the limit
|
||||||
|
@ -1116,6 +1123,121 @@ public class FSXAttrBaseTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tests the "unreadable by superuser" xattr which denies access to a
|
||||||
|
* file for the superuser. See HDFS-6705 for details.
|
||||||
|
*/
|
||||||
|
@Test(timeout = 120000)
|
||||||
|
public void testUnreadableBySuperuserXAttr() throws Exception {
|
||||||
|
// Run tests as superuser...
|
||||||
|
doTestUnreadableBySuperuserXAttr(fs, true);
|
||||||
|
|
||||||
|
// ...and again as non-superuser
|
||||||
|
final UserGroupInformation user = UserGroupInformation.
|
||||||
|
createUserForTesting("user", new String[] { "mygroup" });
|
||||||
|
user.doAs(new PrivilegedExceptionAction<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object run() throws Exception {
|
||||||
|
final FileSystem userFs = dfsCluster.getFileSystem();
|
||||||
|
doTestUnreadableBySuperuserXAttr(userFs, false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doTestUnreadableBySuperuserXAttr(FileSystem userFs,
|
||||||
|
boolean expectOpenFailure) throws Exception {
|
||||||
|
|
||||||
|
FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short) 0777));
|
||||||
|
DFSTestUtil.createFile(userFs, filePath, 8192, (short) 1, 0xFEED);
|
||||||
|
try {
|
||||||
|
doTUBSXAInt(userFs, expectOpenFailure);
|
||||||
|
// Deleting the file is allowed.
|
||||||
|
userFs.delete(filePath, false);
|
||||||
|
} finally {
|
||||||
|
fs.delete(path, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doTUBSXAInt(FileSystem userFs, boolean expectOpenFailure)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
// Test that xattr can't be set on a dir
|
||||||
|
try {
|
||||||
|
userFs.setXAttr(path, security1, null, EnumSet.of(XAttrSetFlag.CREATE));
|
||||||
|
} catch (IOException e) {
|
||||||
|
// WebHDFS throws IOException instead of RemoteException
|
||||||
|
GenericTestUtils.assertExceptionContains("Can only set '" +
|
||||||
|
SECURITY_XATTR_UNREADABLE_BY_SUPERUSER + "' on a file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that xattr can actually be set. Repeatedly.
|
||||||
|
userFs.setXAttr(filePath, security1, null,
|
||||||
|
EnumSet.of(XAttrSetFlag.CREATE));
|
||||||
|
verifySecurityXAttrExists(userFs);
|
||||||
|
userFs.setXAttr(filePath, security1, null, EnumSet.of(XAttrSetFlag.CREATE,
|
||||||
|
XAttrSetFlag.REPLACE));
|
||||||
|
verifySecurityXAttrExists(userFs);
|
||||||
|
|
||||||
|
// Test that the xattr can't be deleted by anyone.
|
||||||
|
try {
|
||||||
|
userFs.removeXAttr(filePath, security1);
|
||||||
|
Assert.fail("Removing security xattr should fail.");
|
||||||
|
} catch (AccessControlException e) {
|
||||||
|
GenericTestUtils.assertExceptionContains("The xattr '" +
|
||||||
|
SECURITY_XATTR_UNREADABLE_BY_SUPERUSER + "' can not be deleted.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that xattr can be read.
|
||||||
|
verifySecurityXAttrExists(userFs);
|
||||||
|
|
||||||
|
// Test that a value can't be set for the xattr.
|
||||||
|
try {
|
||||||
|
userFs.setXAttr(filePath, security1,
|
||||||
|
value1,EnumSet.of(XAttrSetFlag.REPLACE));
|
||||||
|
fail("Should have thrown on attempt to set value");
|
||||||
|
} catch (AccessControlException e) {
|
||||||
|
GenericTestUtils.assertExceptionContains("Values are not allowed", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that unreadable by superuser xattr appears in listXAttrs results
|
||||||
|
// (for superuser and non-superuser)
|
||||||
|
final List<String> xattrNames = userFs.listXAttrs(filePath);
|
||||||
|
assertTrue(xattrNames.contains(security1));
|
||||||
|
assertTrue(xattrNames.size() == 1);
|
||||||
|
|
||||||
|
verifyFileAccess(userFs, expectOpenFailure);
|
||||||
|
|
||||||
|
// Rename of the file is allowed by anyone.
|
||||||
|
Path toPath = new Path(filePath.toString() + "x");
|
||||||
|
userFs.rename(filePath, toPath);
|
||||||
|
userFs.rename(toPath, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifySecurityXAttrExists(FileSystem userFs) throws Exception {
|
||||||
|
try {
|
||||||
|
final Map<String, byte[]> xattrs = userFs.getXAttrs(filePath);
|
||||||
|
Assert.assertEquals(1, xattrs.size());
|
||||||
|
Assert.assertNotNull(xattrs.get(security1));
|
||||||
|
Assert.assertArrayEquals("expected empty byte[] from getXAttr",
|
||||||
|
new byte[0], userFs.getXAttr(filePath, security1));
|
||||||
|
|
||||||
|
} catch (AccessControlException e) {
|
||||||
|
fail("getXAttrs failed but expected it to succeed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyFileAccess(FileSystem userFs, boolean expectOpenFailure)
|
||||||
|
throws Exception {
|
||||||
|
// Test that a file with the xattr can or can't be opened.
|
||||||
|
try {
|
||||||
|
userFs.open(filePath);
|
||||||
|
assertFalse("open succeeded but expected it to fail", expectOpenFailure);
|
||||||
|
} catch (AccessControlException e) {
|
||||||
|
assertTrue("open failed but expected it to succeed", expectOpenFailure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a FileSystem for the super-user.
|
* Creates a FileSystem for the super-user.
|
||||||
*
|
*
|
||||||
|
|
|
@ -125,6 +125,79 @@
|
||||||
</comparators>
|
</comparators>
|
||||||
</test>
|
</test>
|
||||||
|
|
||||||
|
<test>
|
||||||
|
<description>setfattr : Add the unreadable by superuser xattr to security namespace</description>
|
||||||
|
<test-commands>
|
||||||
|
<command>-fs NAMENODE -touchz /file1</command>
|
||||||
|
<command>-fs NAMENODE -setfattr -n security.hdfs.unreadable.by.superuser /file1</command>
|
||||||
|
<command>-fs NAMENODE -getfattr -d /file1</command>
|
||||||
|
</test-commands>
|
||||||
|
<cleanup-commands>
|
||||||
|
<command>-fs NAMENODE -rm /file1</command>
|
||||||
|
</cleanup-commands>
|
||||||
|
<comparators>
|
||||||
|
<comparator>
|
||||||
|
<type>SubstringComparator</type>
|
||||||
|
<expected-output>security.hdfs.unreadable.by.superuser</expected-output>
|
||||||
|
</comparator>
|
||||||
|
</comparators>
|
||||||
|
</test>
|
||||||
|
|
||||||
|
<test>
|
||||||
|
<description>setfattr : Try to delete the unreadable by superuser xattr from security namespace</description>
|
||||||
|
<test-commands>
|
||||||
|
<command>-fs NAMENODE -touchz /file1</command>
|
||||||
|
<command>-fs NAMENODE -setfattr -n security.hdfs.unreadable.by.superuser /file1</command>
|
||||||
|
<command>-fs NAMENODE -setfattr -x security.hdfs.unreadable.by.superuser /file1</command>
|
||||||
|
</test-commands>
|
||||||
|
<cleanup-commands>
|
||||||
|
<command>-fs NAMENODE -rm /file1</command>
|
||||||
|
</cleanup-commands>
|
||||||
|
<comparators>
|
||||||
|
<comparator>
|
||||||
|
<type>SubstringComparator</type>
|
||||||
|
<expected-output>can not be deleted</expected-output>
|
||||||
|
</comparator>
|
||||||
|
</comparators>
|
||||||
|
</test>
|
||||||
|
|
||||||
|
<test>
|
||||||
|
<description>setfattr : Try to read a file protected by the unreadable by superuser xattr</description>
|
||||||
|
<test-commands>
|
||||||
|
<command>-fs NAMENODE -touchz /file1</command>
|
||||||
|
<command>-fs NAMENODE -setfattr -n security.hdfs.unreadable.by.superuser /file1</command>
|
||||||
|
<command>-fs NAMENODE -get /file1 /tmp/file1</command>
|
||||||
|
</test-commands>
|
||||||
|
<cleanup-commands>
|
||||||
|
<command>-fs NAMENODE -rm /file1</command>
|
||||||
|
<command>rm /tmp/file1</command>
|
||||||
|
</cleanup-commands>
|
||||||
|
<comparators>
|
||||||
|
<comparator>
|
||||||
|
<type>SubstringComparator</type>
|
||||||
|
<expected-output>Access is denied</expected-output>
|
||||||
|
</comparator>
|
||||||
|
</comparators>
|
||||||
|
</test>
|
||||||
|
|
||||||
|
<test>
|
||||||
|
<description>setfattr : Try to add a value to the unreadable by superuser xattr</description>
|
||||||
|
<test-commands>
|
||||||
|
<command>-fs NAMENODE -touchz /file1</command>
|
||||||
|
<command>-fs NAMENODE -setfattr -n security.hdfs.unreadable.by.superuser /file1</command>
|
||||||
|
<command>-fs NAMENODE -setfattr -n security.hdfs.unreadable.by.superuser -v 1234 /file1</command>
|
||||||
|
</test-commands>
|
||||||
|
<cleanup-commands>
|
||||||
|
<command>-fs NAMENODE -rm /file1</command>
|
||||||
|
</cleanup-commands>
|
||||||
|
<comparators>
|
||||||
|
<comparator>
|
||||||
|
<type>SubstringComparator</type>
|
||||||
|
<expected-output>Values are not allowed</expected-output>
|
||||||
|
</comparator>
|
||||||
|
</comparators>
|
||||||
|
</test>
|
||||||
|
|
||||||
<test>
|
<test>
|
||||||
<description>setfattr : Add an xattr of raw namespace</description>
|
<description>setfattr : Add an xattr of raw namespace</description>
|
||||||
<test-commands>
|
<test-commands>
|
||||||
|
|
Loading…
Reference in New Issue