HADOOP-10427. KeyProvider implementations should be thread safe. (tucu)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1586103 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Alejandro Abdelnur 2014-04-09 19:43:35 +00:00
parent 9a2ec694fe
commit 98a98ea0c5
4 changed files with 181 additions and 129 deletions

View File

@ -130,6 +130,8 @@ Trunk (Unreleased)
HADOOP-10432. Refactor SSLFactory to expose static method to determine HADOOP-10432. Refactor SSLFactory to expose static method to determine
HostnameVerifier. (tucu) HostnameVerifier. (tucu)
HADOOP-10427. KeyProvider implementations should be thread safe. (tucu)
BUG FIXES BUG FIXES
HADOOP-9451. Fault single-layer config if node group topology is enabled. HADOOP-9451. Fault single-layer config if node group topology is enabled.

View File

@ -43,6 +43,9 @@ import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/** /**
* KeyProvider based on Java's KeyStore file format. The file may be stored in * KeyProvider based on Java's KeyStore file format. The file may be stored in
@ -73,6 +76,8 @@ public class JavaKeyStoreProvider extends KeyProvider {
private final KeyStore keyStore; private final KeyStore keyStore;
private final char[] password; private final char[] password;
private boolean changed = false; private boolean changed = false;
private Lock readLock;
private Lock writeLock;
private final Map<String, Metadata> cache = new HashMap<String, Metadata>(); private final Map<String, Metadata> cache = new HashMap<String, Metadata>();
@ -107,138 +112,171 @@ public class JavaKeyStoreProvider extends KeyProvider {
} catch (CertificateException e) { } catch (CertificateException e) {
throw new IOException("Can't load keystore " + path, e); throw new IOException("Can't load keystore " + path, e);
} }
ReadWriteLock lock = new ReentrantReadWriteLock(true);
readLock = lock.readLock();
writeLock = lock.writeLock();
} }
@Override @Override
public KeyVersion getKeyVersion(String versionName) throws IOException { public KeyVersion getKeyVersion(String versionName) throws IOException {
SecretKeySpec key = null; readLock.lock();
try { try {
if (!keyStore.containsAlias(versionName)) { SecretKeySpec key = null;
return null; try {
if (!keyStore.containsAlias(versionName)) {
return null;
}
key = (SecretKeySpec) keyStore.getKey(versionName, password);
} catch (KeyStoreException e) {
throw new IOException("Can't get key " + versionName + " from " +
path, e);
} catch (NoSuchAlgorithmException e) {
throw new IOException("Can't get algorithm for key " + key + " from " +
path, e);
} catch (UnrecoverableKeyException e) {
throw new IOException("Can't recover key " + key + " from " + path, e);
} }
key = (SecretKeySpec) keyStore.getKey(versionName, password); return new KeyVersion(versionName, key.getEncoded());
} catch (KeyStoreException e) { } finally {
throw new IOException("Can't get key " + versionName + " from " + readLock.unlock();
path, e);
} catch (NoSuchAlgorithmException e) {
throw new IOException("Can't get algorithm for key " + key + " from " +
path, e);
} catch (UnrecoverableKeyException e) {
throw new IOException("Can't recover key " + key + " from " + path, e);
} }
return new KeyVersion(versionName, key.getEncoded());
} }
@Override @Override
public List<String> getKeys() throws IOException { public List<String> getKeys() throws IOException {
ArrayList<String> list = new ArrayList<String>(); readLock.lock();
String alias = null;
try { try {
Enumeration<String> e = keyStore.aliases(); ArrayList<String> list = new ArrayList<String>();
while (e.hasMoreElements()) { String alias = null;
alias = e.nextElement(); try {
// only include the metadata key names in the list of names Enumeration<String> e = keyStore.aliases();
if (!alias.contains("@")) { while (e.hasMoreElements()) {
list.add(alias); alias = e.nextElement();
} // only include the metadata key names in the list of names
if (!alias.contains("@")) {
list.add(alias);
}
}
} catch (KeyStoreException e) {
throw new IOException("Can't get key " + alias + " from " + path, e);
} }
} catch (KeyStoreException e) { return list;
throw new IOException("Can't get key " + alias + " from " + path, e); } finally {
readLock.unlock();
} }
return list;
} }
@Override @Override
public List<KeyVersion> getKeyVersions(String name) throws IOException { public List<KeyVersion> getKeyVersions(String name) throws IOException {
List<KeyVersion> list = new ArrayList<KeyVersion>(); readLock.lock();
Metadata km = getMetadata(name); try {
if (km != null) { List<KeyVersion> list = new ArrayList<KeyVersion>();
int latestVersion = km.getVersions(); Metadata km = getMetadata(name);
KeyVersion v = null; if (km != null) {
String versionName = null; int latestVersion = km.getVersions();
for (int i = 0; i < latestVersion; i++) { KeyVersion v = null;
versionName = buildVersionName(name, i); String versionName = null;
v = getKeyVersion(versionName); for (int i = 0; i < latestVersion; i++) {
if (v != null) { versionName = buildVersionName(name, i);
list.add(v); v = getKeyVersion(versionName);
if (v != null) {
list.add(v);
}
} }
} }
return list;
} finally {
readLock.unlock();
} }
return list;
} }
@Override @Override
public Metadata getMetadata(String name) throws IOException { public Metadata getMetadata(String name) throws IOException {
if (cache.containsKey(name)) { readLock.lock();
return cache.get(name);
}
try { try {
if (!keyStore.containsAlias(name)) { if (cache.containsKey(name)) {
return null; return cache.get(name);
} }
Metadata meta = ((KeyMetadata) keyStore.getKey(name, password)).metadata; try {
cache.put(name, meta); if (!keyStore.containsAlias(name)) {
return meta; return null;
} catch (KeyStoreException e) { }
throw new IOException("Can't get metadata for " + name + Metadata meta = ((KeyMetadata) keyStore.getKey(name, password)).metadata;
" from keystore " + path, e); cache.put(name, meta);
} catch (NoSuchAlgorithmException e) { return meta;
throw new IOException("Can't get algorithm for " + name + } catch (KeyStoreException e) {
" from keystore " + path, e); throw new IOException("Can't get metadata for " + name +
} catch (UnrecoverableKeyException e) { " from keystore " + path, e);
throw new IOException("Can't recover key for " + name + } catch (NoSuchAlgorithmException e) {
" from keystore " + path, e); throw new IOException("Can't get algorithm for " + name +
" from keystore " + path, e);
} catch (UnrecoverableKeyException e) {
throw new IOException("Can't recover key for " + name +
" from keystore " + path, e);
}
} finally {
readLock.unlock();
} }
} }
@Override @Override
public KeyVersion createKey(String name, byte[] material, public KeyVersion createKey(String name, byte[] material,
Options options) throws IOException { Options options) throws IOException {
writeLock.lock();
try { try {
if (keyStore.containsAlias(name) || cache.containsKey(name)) { try {
throw new IOException("Key " + name + " already exists in " + this); if (keyStore.containsAlias(name) || cache.containsKey(name)) {
throw new IOException("Key " + name + " already exists in " + this);
}
} catch (KeyStoreException e) {
throw new IOException("Problem looking up key " + name + " in " + this,
e);
} }
} catch (KeyStoreException e) { Metadata meta = new Metadata(options.getCipher(), options.getBitLength(),
throw new IOException("Problem looking up key " + name + " in " + this, new Date(), 1);
e); if (options.getBitLength() != 8 * material.length) {
throw new IOException("Wrong key length. Required " +
options.getBitLength() + ", but got " + (8 * material.length));
}
cache.put(name, meta);
String versionName = buildVersionName(name, 0);
return innerSetKeyVersion(versionName, material, meta.getCipher());
} finally {
writeLock.unlock();
} }
Metadata meta = new Metadata(options.getCipher(), options.getBitLength(),
new Date(), 1);
if (options.getBitLength() != 8 * material.length) {
throw new IOException("Wrong key length. Required " +
options.getBitLength() + ", but got " + (8 * material.length));
}
cache.put(name, meta);
String versionName = buildVersionName(name, 0);
return innerSetKeyVersion(versionName, material, meta.getCipher());
} }
@Override @Override
public void deleteKey(String name) throws IOException { public void deleteKey(String name) throws IOException {
Metadata meta = getMetadata(name); writeLock.lock();
if (meta == null) { try {
throw new IOException("Key " + name + " does not exist in " + this); Metadata meta = getMetadata(name);
} if (meta == null) {
for(int v=0; v < meta.getVersions(); ++v) { throw new IOException("Key " + name + " does not exist in " + this);
String versionName = buildVersionName(name, v); }
for(int v=0; v < meta.getVersions(); ++v) {
String versionName = buildVersionName(name, v);
try {
if (keyStore.containsAlias(versionName)) {
keyStore.deleteEntry(versionName);
}
} catch (KeyStoreException e) {
throw new IOException("Problem removing " + versionName + " from " +
this, e);
}
}
try { try {
if (keyStore.containsAlias(versionName)) { if (keyStore.containsAlias(name)) {
keyStore.deleteEntry(versionName); keyStore.deleteEntry(name);
} }
} catch (KeyStoreException e) { } catch (KeyStoreException e) {
throw new IOException("Problem removing " + versionName + " from " + throw new IOException("Problem removing " + name + " from " + this, e);
this, e);
} }
cache.remove(name);
changed = true;
} finally {
writeLock.unlock();
} }
try {
if (keyStore.containsAlias(name)) {
keyStore.deleteEntry(name);
}
} catch (KeyStoreException e) {
throw new IOException("Problem removing " + name + " from " + this, e);
}
cache.remove(name);
changed = true;
} }
KeyVersion innerSetKeyVersion(String versionName, byte[] material, KeyVersion innerSetKeyVersion(String versionName, byte[] material,
@ -257,47 +295,57 @@ public class JavaKeyStoreProvider extends KeyProvider {
@Override @Override
public KeyVersion rollNewVersion(String name, public KeyVersion rollNewVersion(String name,
byte[] material) throws IOException { byte[] material) throws IOException {
Metadata meta = getMetadata(name); writeLock.lock();
if (meta == null) { try {
throw new IOException("Key " + name + " not found"); Metadata meta = getMetadata(name);
if (meta == null) {
throw new IOException("Key " + name + " not found");
}
if (meta.getBitLength() != 8 * material.length) {
throw new IOException("Wrong key length. Required " +
meta.getBitLength() + ", but got " + (8 * material.length));
}
int nextVersion = meta.addVersion();
String versionName = buildVersionName(name, nextVersion);
return innerSetKeyVersion(versionName, material, meta.getCipher());
} finally {
writeLock.unlock();
} }
if (meta.getBitLength() != 8 * material.length) {
throw new IOException("Wrong key length. Required " +
meta.getBitLength() + ", but got " + (8 * material.length));
}
int nextVersion = meta.addVersion();
String versionName = buildVersionName(name, nextVersion);
return innerSetKeyVersion(versionName, material, meta.getCipher());
} }
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
if (!changed) { writeLock.lock();
return;
}
// put all of the updates into the keystore
for(Map.Entry<String, Metadata> entry: cache.entrySet()) {
try {
keyStore.setKeyEntry(entry.getKey(), new KeyMetadata(entry.getValue()),
password, null);
} catch (KeyStoreException e) {
throw new IOException("Can't set metadata key " + entry.getKey(),e );
}
}
// write out the keystore
FSDataOutputStream out = FileSystem.create(fs, path, permissions);
try { try {
keyStore.store(out, password); if (!changed) {
} catch (KeyStoreException e) { return;
throw new IOException("Can't store keystore " + this, e); }
} catch (NoSuchAlgorithmException e) { // put all of the updates into the keystore
throw new IOException("No such algorithm storing keystore " + this, e); for(Map.Entry<String, Metadata> entry: cache.entrySet()) {
} catch (CertificateException e) { try {
throw new IOException("Certificate exception storing keystore " + this, keyStore.setKeyEntry(entry.getKey(), new KeyMetadata(entry.getValue()),
e); password, null);
} catch (KeyStoreException e) {
throw new IOException("Can't set metadata key " + entry.getKey(),e );
}
}
// write out the keystore
FSDataOutputStream out = FileSystem.create(fs, path, permissions);
try {
keyStore.store(out, password);
} catch (KeyStoreException e) {
throw new IOException("Can't store keystore " + this, e);
} catch (NoSuchAlgorithmException e) {
throw new IOException("No such algorithm storing keystore " + this, e);
} catch (CertificateException e) {
throw new IOException("Certificate exception storing keystore " + this,
e);
}
out.close();
changed = false;
} finally {
writeLock.unlock();
} }
out.close();
changed = false;
} }
@Override @Override

View File

@ -39,6 +39,8 @@ import org.apache.hadoop.fs.Path;
* abstraction to separate key storage from users of encryption. It * abstraction to separate key storage from users of encryption. It
* is intended to support getting or storing keys in a variety of ways, * is intended to support getting or storing keys in a variety of ways,
* including third party bindings. * including third party bindings.
* <P/>
* <code>KeyProvider</code> implementations must be thread safe.
*/ */
@InterfaceAudience.Public @InterfaceAudience.Public
@InterfaceStability.Unstable @InterfaceStability.Unstable

View File

@ -55,7 +55,7 @@ public class UserProvider extends KeyProvider {
} }
@Override @Override
public KeyVersion getKeyVersion(String versionName) { public synchronized KeyVersion getKeyVersion(String versionName) {
byte[] bytes = credentials.getSecretKey(new Text(versionName)); byte[] bytes = credentials.getSecretKey(new Text(versionName));
if (bytes == null) { if (bytes == null) {
return null; return null;
@ -64,7 +64,7 @@ public class UserProvider extends KeyProvider {
} }
@Override @Override
public Metadata getMetadata(String name) throws IOException { public synchronized Metadata getMetadata(String name) throws IOException {
if (cache.containsKey(name)) { if (cache.containsKey(name)) {
return cache.get(name); return cache.get(name);
} }
@ -78,7 +78,7 @@ public class UserProvider extends KeyProvider {
} }
@Override @Override
public KeyVersion createKey(String name, byte[] material, public synchronized KeyVersion createKey(String name, byte[] material,
Options options) throws IOException { Options options) throws IOException {
Text nameT = new Text(name); Text nameT = new Text(name);
if (credentials.getSecretKey(nameT) != null) { if (credentials.getSecretKey(nameT) != null) {
@ -98,7 +98,7 @@ public class UserProvider extends KeyProvider {
} }
@Override @Override
public void deleteKey(String name) throws IOException { public synchronized void deleteKey(String name) throws IOException {
Metadata meta = getMetadata(name); Metadata meta = getMetadata(name);
if (meta == null) { if (meta == null) {
throw new IOException("Key " + name + " does not exist in " + this); throw new IOException("Key " + name + " does not exist in " + this);
@ -111,7 +111,7 @@ public class UserProvider extends KeyProvider {
} }
@Override @Override
public KeyVersion rollNewVersion(String name, public synchronized KeyVersion rollNewVersion(String name,
byte[] material) throws IOException { byte[] material) throws IOException {
Metadata meta = getMetadata(name); Metadata meta = getMetadata(name);
if (meta == null) { if (meta == null) {
@ -134,7 +134,7 @@ public class UserProvider extends KeyProvider {
} }
@Override @Override
public void flush() { public synchronized void flush() {
user.addCredentials(credentials); user.addCredentials(credentials);
} }
@ -151,7 +151,7 @@ public class UserProvider extends KeyProvider {
} }
@Override @Override
public List<String> getKeys() throws IOException { public synchronized List<String> getKeys() throws IOException {
List<String> list = new ArrayList<String>(); List<String> list = new ArrayList<String>();
List<Text> keys = credentials.getAllSecretKeys(); List<Text> keys = credentials.getAllSecretKeys();
for (Text key : keys) { for (Text key : keys) {
@ -163,7 +163,7 @@ public class UserProvider extends KeyProvider {
} }
@Override @Override
public List<KeyVersion> getKeyVersions(String name) throws IOException { public synchronized List<KeyVersion> getKeyVersions(String name) throws IOException {
List<KeyVersion> list = new ArrayList<KeyVersion>(); List<KeyVersion> list = new ArrayList<KeyVersion>();
Metadata km = getMetadata(name); Metadata km = getMetadata(name);
if (km != null) { if (km != null) {