HADOOP-11110. JavaKeystoreProvider should not report a key as created if it was not flushed to the backing file. (Arun Suresh via wang)
This commit is contained in:
parent
f0293f11a8
commit
a78953c974
|
@ -761,6 +761,9 @@ Release 2.6.0 - UNRELEASED
|
||||||
HDFS-7157. Using Time.now() for recording start/end time of reconfiguration
|
HDFS-7157. Using Time.now() for recording start/end time of reconfiguration
|
||||||
tasks (Lei Xu via cmccabe)
|
tasks (Lei Xu via cmccabe)
|
||||||
|
|
||||||
|
HADOOP-1110. JavaKeystoreProvider should not report a key as created if it
|
||||||
|
was not flushed to the backing file.
|
||||||
|
|
||||||
BREAKDOWN OF HDFS-6134 AND HADOOP-10150 SUBTASKS AND RELATED JIRAS
|
BREAKDOWN OF HDFS-6134 AND HADOOP-10150 SUBTASKS AND RELATED JIRAS
|
||||||
|
|
||||||
HADOOP-10734. Implement high-performance secure random number sources.
|
HADOOP-10734. Implement high-performance secure random number sources.
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.apache.hadoop.crypto.key;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.classification.InterfaceAudience.Private;
|
||||||
import org.apache.hadoop.conf.Configuration;
|
import org.apache.hadoop.conf.Configuration;
|
||||||
import org.apache.hadoop.fs.FSDataOutputStream;
|
import org.apache.hadoop.fs.FSDataOutputStream;
|
||||||
import org.apache.hadoop.fs.FileStatus;
|
import org.apache.hadoop.fs.FileStatus;
|
||||||
|
@ -30,6 +31,8 @@ import org.apache.hadoop.security.ProviderUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -107,6 +110,20 @@ public class JavaKeyStoreProvider extends KeyProvider {
|
||||||
|
|
||||||
private final Map<String, Metadata> cache = new HashMap<String, Metadata>();
|
private final Map<String, Metadata> cache = new HashMap<String, Metadata>();
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
JavaKeyStoreProvider(JavaKeyStoreProvider other) {
|
||||||
|
super(new Configuration());
|
||||||
|
uri = other.uri;
|
||||||
|
path = other.path;
|
||||||
|
fs = other.fs;
|
||||||
|
permissions = other.permissions;
|
||||||
|
keyStore = other.keyStore;
|
||||||
|
password = other.password;
|
||||||
|
changed = other.changed;
|
||||||
|
readLock = other.readLock;
|
||||||
|
writeLock = other.writeLock;
|
||||||
|
}
|
||||||
|
|
||||||
private JavaKeyStoreProvider(URI uri, Configuration conf) throws IOException {
|
private JavaKeyStoreProvider(URI uri, Configuration conf) throws IOException {
|
||||||
super(conf);
|
super(conf);
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
|
@ -501,6 +518,7 @@ public class JavaKeyStoreProvider extends KeyProvider {
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
Path newPath = constructNewPath(path);
|
Path newPath = constructNewPath(path);
|
||||||
Path oldPath = constructOldPath(path);
|
Path oldPath = constructOldPath(path);
|
||||||
|
Path resetPath = path;
|
||||||
writeLock.lock();
|
writeLock.lock();
|
||||||
try {
|
try {
|
||||||
if (!changed) {
|
if (!changed) {
|
||||||
|
@ -527,6 +545,9 @@ public class JavaKeyStoreProvider extends KeyProvider {
|
||||||
|
|
||||||
// Save old File first
|
// Save old File first
|
||||||
boolean fileExisted = backupToOld(oldPath);
|
boolean fileExisted = backupToOld(oldPath);
|
||||||
|
if (fileExisted) {
|
||||||
|
resetPath = oldPath;
|
||||||
|
}
|
||||||
// write out the keystore
|
// write out the keystore
|
||||||
// Write to _NEW path first :
|
// Write to _NEW path first :
|
||||||
try {
|
try {
|
||||||
|
@ -534,16 +555,34 @@ public class JavaKeyStoreProvider extends KeyProvider {
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
// rename _OLD back to curent and throw Exception
|
// rename _OLD back to curent and throw Exception
|
||||||
revertFromOld(oldPath, fileExisted);
|
revertFromOld(oldPath, fileExisted);
|
||||||
|
resetPath = path;
|
||||||
throw ioe;
|
throw ioe;
|
||||||
}
|
}
|
||||||
// Rename _NEW to CURRENT and delete _OLD
|
// Rename _NEW to CURRENT and delete _OLD
|
||||||
cleanupNewAndOld(newPath, oldPath);
|
cleanupNewAndOld(newPath, oldPath);
|
||||||
changed = false;
|
changed = false;
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
resetKeyStoreState(resetPath);
|
||||||
|
throw ioe;
|
||||||
} finally {
|
} finally {
|
||||||
writeLock.unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resetKeyStoreState(Path path) {
|
||||||
|
LOG.debug("Could not flush Keystore.."
|
||||||
|
+ "attempting to reset to previous state !!");
|
||||||
|
// 1) flush cache
|
||||||
|
cache.clear();
|
||||||
|
// 2) load keyStore from previous path
|
||||||
|
try {
|
||||||
|
loadFromPath(path, password);
|
||||||
|
LOG.debug("KeyStore resetting to previously flushed state !!");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.debug("Could not reset Keystore to previous state", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void cleanupNewAndOld(Path newPath, Path oldPath) throws IOException {
|
private void cleanupNewAndOld(Path newPath, Path oldPath) throws IOException {
|
||||||
// Rename _NEW to CURRENT
|
// Rename _NEW to CURRENT
|
||||||
renameOrFail(newPath, path);
|
renameOrFail(newPath, path);
|
||||||
|
@ -553,7 +592,7 @@ public class JavaKeyStoreProvider extends KeyProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeToNew(Path newPath) throws IOException {
|
protected void writeToNew(Path newPath) throws IOException {
|
||||||
FSDataOutputStream out =
|
FSDataOutputStream out =
|
||||||
FileSystem.create(fs, newPath, permissions);
|
FileSystem.create(fs, newPath, permissions);
|
||||||
try {
|
try {
|
||||||
|
@ -570,14 +609,7 @@ public class JavaKeyStoreProvider extends KeyProvider {
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revertFromOld(Path oldPath, boolean fileExisted)
|
protected boolean backupToOld(Path oldPath)
|
||||||
throws IOException {
|
|
||||||
if (fileExisted) {
|
|
||||||
renameOrFail(oldPath, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean backupToOld(Path oldPath)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
boolean fileExisted = false;
|
boolean fileExisted = false;
|
||||||
if (fs.exists(path)) {
|
if (fs.exists(path)) {
|
||||||
|
@ -587,6 +619,14 @@ public class JavaKeyStoreProvider extends KeyProvider {
|
||||||
return fileExisted;
|
return fileExisted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void revertFromOld(Path oldPath, boolean fileExisted)
|
||||||
|
throws IOException {
|
||||||
|
if (fileExisted) {
|
||||||
|
renameOrFail(oldPath, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void renameOrFail(Path src, Path dest)
|
private void renameOrFail(Path src, Path dest)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (!fs.rename(src, dest)) {
|
if (!fs.rename(src, dest)) {
|
||||||
|
|
|
@ -345,8 +345,8 @@ public class KeyShell extends Configured implements Tool {
|
||||||
+ provider + "\n for key name: " + keyName);
|
+ provider + "\n for key name: " + keyName);
|
||||||
try {
|
try {
|
||||||
provider.rollNewVersion(keyName);
|
provider.rollNewVersion(keyName);
|
||||||
out.println(keyName + " has been successfully rolled.");
|
|
||||||
provider.flush();
|
provider.flush();
|
||||||
|
out.println(keyName + " has been successfully rolled.");
|
||||||
printProviderWritten();
|
printProviderWritten();
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
out.println("Cannot roll key: " + keyName + " within KeyProvider: "
|
out.println("Cannot roll key: " + keyName + " within KeyProvider: "
|
||||||
|
@ -418,8 +418,8 @@ public class KeyShell extends Configured implements Tool {
|
||||||
if (cont) {
|
if (cont) {
|
||||||
try {
|
try {
|
||||||
provider.deleteKey(keyName);
|
provider.deleteKey(keyName);
|
||||||
out.println(keyName + " has been successfully deleted.");
|
|
||||||
provider.flush();
|
provider.flush();
|
||||||
|
out.println(keyName + " has been successfully deleted.");
|
||||||
printProviderWritten();
|
printProviderWritten();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
out.println(keyName + " has not been deleted.");
|
out.println(keyName + " has not been deleted.");
|
||||||
|
@ -479,9 +479,9 @@ public class KeyShell extends Configured implements Tool {
|
||||||
warnIfTransientProvider();
|
warnIfTransientProvider();
|
||||||
try {
|
try {
|
||||||
provider.createKey(keyName, options);
|
provider.createKey(keyName, options);
|
||||||
|
provider.flush();
|
||||||
out.println(keyName + " has been successfully created with options "
|
out.println(keyName + " has been successfully created with options "
|
||||||
+ options.toString() + ".");
|
+ options.toString() + ".");
|
||||||
provider.flush();
|
|
||||||
printProviderWritten();
|
printProviderWritten();
|
||||||
} catch (InvalidParameterException e) {
|
} catch (InvalidParameterException e) {
|
||||||
out.println(keyName + " has not been created. " + e.getMessage());
|
out.println(keyName + " has not been created. " + e.getMessage());
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.crypto.key;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
|
||||||
|
public class FailureInjectingJavaKeyStoreProvider extends JavaKeyStoreProvider {
|
||||||
|
|
||||||
|
public static final String SCHEME_NAME = "failjceks";
|
||||||
|
|
||||||
|
private boolean backupFail = false;
|
||||||
|
private boolean writeFail = false;
|
||||||
|
FailureInjectingJavaKeyStoreProvider(JavaKeyStoreProvider prov) {
|
||||||
|
super(prov);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackupFail(boolean b) {
|
||||||
|
backupFail = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWriteFail(boolean b) {
|
||||||
|
backupFail = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failure injection methods..
|
||||||
|
@Override
|
||||||
|
public void writeToNew(Path newPath) throws IOException {
|
||||||
|
if (writeFail) {
|
||||||
|
throw new IOException("Injecting failure on write");
|
||||||
|
}
|
||||||
|
super.writeToNew(newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean backupToOld(Path oldPath) throws IOException {
|
||||||
|
if (backupFail) {
|
||||||
|
throw new IOException("Inejection Failure on backup");
|
||||||
|
}
|
||||||
|
return super.backupToOld(oldPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Factory extends KeyProviderFactory {
|
||||||
|
@Override
|
||||||
|
public KeyProvider createProvider(URI providerName,
|
||||||
|
Configuration conf) throws IOException {
|
||||||
|
if (SCHEME_NAME.equals(providerName.getScheme())) {
|
||||||
|
try {
|
||||||
|
return new FailureInjectingJavaKeyStoreProvider(
|
||||||
|
(JavaKeyStoreProvider) new JavaKeyStoreProvider.Factory()
|
||||||
|
.createProvider(
|
||||||
|
new URI(providerName.toString().replace(SCHEME_NAME,
|
||||||
|
JavaKeyStoreProvider.SCHEME_NAME)), conf));
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import org.junit.Test;
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
public class TestKeyProviderFactory {
|
public class TestKeyProviderFactory {
|
||||||
|
|
||||||
|
@ -171,6 +172,7 @@ public class TestKeyProviderFactory {
|
||||||
assertEquals("Key no-such-key not found", e.getMessage());
|
assertEquals("Key no-such-key not found", e.getMessage());
|
||||||
}
|
}
|
||||||
provider.flush();
|
provider.flush();
|
||||||
|
|
||||||
// get a new instance of the provider to ensure it was saved correctly
|
// get a new instance of the provider to ensure it was saved correctly
|
||||||
provider = KeyProviderFactory.getProviders(conf).get(0);
|
provider = KeyProviderFactory.getProviders(conf).get(0);
|
||||||
assertArrayEquals(new byte[]{2},
|
assertArrayEquals(new byte[]{2},
|
||||||
|
@ -215,6 +217,50 @@ public class TestKeyProviderFactory {
|
||||||
file.delete();
|
file.delete();
|
||||||
conf.set(KeyProviderFactory.KEY_PROVIDER_PATH, ourUrl);
|
conf.set(KeyProviderFactory.KEY_PROVIDER_PATH, ourUrl);
|
||||||
checkSpecificProvider(conf, ourUrl);
|
checkSpecificProvider(conf, ourUrl);
|
||||||
|
|
||||||
|
// START : Test flush error by failure injection
|
||||||
|
conf.set(KeyProviderFactory.KEY_PROVIDER_PATH, ourUrl.replace(
|
||||||
|
JavaKeyStoreProvider.SCHEME_NAME,
|
||||||
|
FailureInjectingJavaKeyStoreProvider.SCHEME_NAME));
|
||||||
|
// get a new instance of the provider to ensure it was saved correctly
|
||||||
|
KeyProvider provider = KeyProviderFactory.getProviders(conf).get(0);
|
||||||
|
// inject failure during keystore write
|
||||||
|
FailureInjectingJavaKeyStoreProvider fProvider =
|
||||||
|
(FailureInjectingJavaKeyStoreProvider) provider;
|
||||||
|
fProvider.setWriteFail(true);
|
||||||
|
provider.createKey("key5", new byte[]{1},
|
||||||
|
KeyProvider.options(conf).setBitLength(8));
|
||||||
|
assertNotNull(provider.getCurrentKey("key5"));
|
||||||
|
try {
|
||||||
|
provider.flush();
|
||||||
|
Assert.fail("Should not succeed");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
// SHould be reset to pre-flush state
|
||||||
|
Assert.assertNull(provider.getCurrentKey("key5"));
|
||||||
|
|
||||||
|
// Un-inject last failure and
|
||||||
|
// inject failure during keystore backup
|
||||||
|
fProvider.setWriteFail(false);
|
||||||
|
fProvider.setBackupFail(true);
|
||||||
|
provider.createKey("key6", new byte[]{1},
|
||||||
|
KeyProvider.options(conf).setBitLength(8));
|
||||||
|
assertNotNull(provider.getCurrentKey("key6"));
|
||||||
|
try {
|
||||||
|
provider.flush();
|
||||||
|
Assert.fail("Should not succeed");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
// SHould be reset to pre-flush state
|
||||||
|
Assert.assertNull(provider.getCurrentKey("key6"));
|
||||||
|
// END : Test flush error by failure injection
|
||||||
|
|
||||||
|
conf.set(KeyProviderFactory.KEY_PROVIDER_PATH, ourUrl.replace(
|
||||||
|
FailureInjectingJavaKeyStoreProvider.SCHEME_NAME,
|
||||||
|
JavaKeyStoreProvider.SCHEME_NAME));
|
||||||
|
|
||||||
Path path = ProviderUtils.unnestUri(new URI(ourUrl));
|
Path path = ProviderUtils.unnestUri(new URI(ourUrl));
|
||||||
FileSystem fs = path.getFileSystem(conf);
|
FileSystem fs = path.getFileSystem(conf);
|
||||||
FileStatus s = fs.getFileStatus(path);
|
FileStatus s = fs.getFileStatus(path);
|
||||||
|
@ -227,7 +273,7 @@ public class TestKeyProviderFactory {
|
||||||
file.delete();
|
file.delete();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
assertTrue(oldFile.exists());
|
assertTrue(oldFile.exists());
|
||||||
KeyProvider provider = KeyProviderFactory.getProviders(conf).get(0);
|
provider = KeyProviderFactory.getProviders(conf).get(0);
|
||||||
assertTrue(file.exists());
|
assertTrue(file.exists());
|
||||||
assertTrue(oldFile + "should be deleted", !oldFile.exists());
|
assertTrue(oldFile + "should be deleted", !oldFile.exists());
|
||||||
verifyAfterReload(file, provider);
|
verifyAfterReload(file, provider);
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
# contributor license agreements. See the NOTICE file distributed with
|
||||||
|
# this work for additional information regarding copyright ownership.
|
||||||
|
# The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
# (the "License"); you may not use this file except in compliance with
|
||||||
|
# the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
org.apache.hadoop.crypto.key.JavaKeyStoreProvider$Factory
|
||||||
|
org.apache.hadoop.crypto.key.UserProvider$Factory
|
||||||
|
org.apache.hadoop.crypto.key.kms.KMSClientProvider$Factory
|
||||||
|
org.apache.hadoop.crypto.key.FailureInjectingJavaKeyStoreProvider$Factory
|
Loading…
Reference in New Issue