| /** |
| * 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.security; |
| |
| import com.google.protobuf.ByteString; |
| |
| import java.io.BufferedInputStream; |
| import java.io.DataInput; |
| import java.io.DataInputStream; |
| import java.io.DataOutput; |
| import java.io.DataOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.io.Charsets; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.classification.InterfaceStability; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.FSDataInputStream; |
| import org.apache.hadoop.fs.FSDataOutputStream; |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.io.IOUtils; |
| import org.apache.hadoop.io.Text; |
| import org.apache.hadoop.io.Writable; |
| import org.apache.hadoop.io.WritableUtils; |
| import org.apache.hadoop.security.token.Token; |
| import org.apache.hadoop.security.token.TokenIdentifier; |
| import org.apache.hadoop.security.proto.SecurityProtos.CredentialsKVProto; |
| import org.apache.hadoop.security.proto.SecurityProtos.CredentialsProto; |
| |
| /** |
| * A class that provides the facilities of reading and writing |
| * secret keys and Tokens. |
| */ |
| @InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"}) |
| @InterfaceStability.Evolving |
| public class Credentials implements Writable { |
| private static final Log LOG = LogFactory.getLog(Credentials.class); |
| |
| private Map<Text, byte[]> secretKeysMap = new HashMap<Text, byte[]>(); |
| private Map<Text, Token<? extends TokenIdentifier>> tokenMap = |
| new HashMap<Text, Token<? extends TokenIdentifier>>(); |
| |
| /** |
| * Create an empty credentials instance. |
| */ |
| public Credentials() { |
| } |
| |
| /** |
| * Create a copy of the given credentials. |
| * @param credentials to copy |
| */ |
| public Credentials(Credentials credentials) { |
| this.addAll(credentials); |
| } |
| |
| /** |
| * Returns the Token object for the alias. |
| * @param alias the alias for the Token |
| * @return token for this alias |
| */ |
| public Token<? extends TokenIdentifier> getToken(Text alias) { |
| return tokenMap.get(alias); |
| } |
| |
| /** |
| * Add a token in the storage (in memory). |
| * @param alias the alias for the key |
| * @param t the token object |
| */ |
| public void addToken(Text alias, Token<? extends TokenIdentifier> t) { |
| if (t == null) { |
| LOG.warn("Null token ignored for " + alias); |
| } else if (tokenMap.put(alias, t) != null) { |
| // Update private tokens |
| Map<Text, Token<? extends TokenIdentifier>> tokensToAdd = |
| new HashMap<>(); |
| for (Map.Entry<Text, Token<? extends TokenIdentifier>> e : |
| tokenMap.entrySet()) { |
| Token<? extends TokenIdentifier> token = e.getValue(); |
| if (token instanceof Token.PrivateToken && |
| ((Token.PrivateToken) token).getPublicService().equals(alias)) { |
| Token<? extends TokenIdentifier> privateToken = |
| new Token.PrivateToken<>(t); |
| privateToken.setService(token.getService()); |
| tokensToAdd.put(e.getKey(), privateToken); |
| } |
| } |
| tokenMap.putAll(tokensToAdd); |
| } |
| } |
| |
| /** |
| * Return all the tokens in the in-memory map. |
| */ |
| public Collection<Token<? extends TokenIdentifier>> getAllTokens() { |
| return tokenMap.values(); |
| } |
| |
| /** |
| * @return number of Tokens in the in-memory map |
| */ |
| public int numberOfTokens() { |
| return tokenMap.size(); |
| } |
| |
| /** |
| * Returns the key bytes for the alias. |
| * @param alias the alias for the key |
| * @return key for this alias |
| */ |
| public byte[] getSecretKey(Text alias) { |
| return secretKeysMap.get(alias); |
| } |
| |
| /** |
| * @return number of keys in the in-memory map |
| */ |
| public int numberOfSecretKeys() { |
| return secretKeysMap.size(); |
| } |
| |
| /** |
| * Set the key for an alias. |
| * @param alias the alias for the key |
| * @param key the key bytes |
| */ |
| public void addSecretKey(Text alias, byte[] key) { |
| secretKeysMap.put(alias, key); |
| } |
| |
| /** |
| * Remove the key for a given alias. |
| * @param alias the alias for the key |
| */ |
| public void removeSecretKey(Text alias) { |
| secretKeysMap.remove(alias); |
| } |
| |
| /** |
| * Return all the secret key entries in the in-memory map. |
| */ |
| public List<Text> getAllSecretKeys() { |
| List<Text> list = new java.util.ArrayList<Text>(); |
| list.addAll(secretKeysMap.keySet()); |
| |
| return list; |
| } |
| |
| /** |
| * Convenience method for reading a token storage file and loading its Tokens. |
| * @param filename |
| * @param conf |
| * @throws IOException |
| */ |
| public static Credentials readTokenStorageFile(Path filename, |
| Configuration conf) |
| throws IOException { |
| FSDataInputStream in = null; |
| Credentials credentials = new Credentials(); |
| try { |
| in = filename.getFileSystem(conf).open(filename); |
| credentials.readTokenStorageStream(in); |
| in.close(); |
| return credentials; |
| } catch(IOException ioe) { |
| throw new IOException("Exception reading " + filename, ioe); |
| } finally { |
| IOUtils.cleanup(LOG, in); |
| } |
| } |
| |
| /** |
| * Convenience method for reading a token storage file and loading its Tokens. |
| * @param filename |
| * @param conf |
| * @throws IOException |
| */ |
| public static Credentials readTokenStorageFile(File filename, |
| Configuration conf) |
| throws IOException { |
| DataInputStream in = null; |
| Credentials credentials = new Credentials(); |
| try { |
| in = new DataInputStream(new BufferedInputStream( |
| new FileInputStream(filename))); |
| credentials.readTokenStorageStream(in); |
| return credentials; |
| } catch(IOException ioe) { |
| throw new IOException("Exception reading " + filename, ioe); |
| } finally { |
| IOUtils.cleanup(LOG, in); |
| } |
| } |
| |
| /** |
| * Convenience method for reading a token from a DataInputStream. |
| */ |
| public void readTokenStorageStream(DataInputStream in) throws IOException { |
| byte[] magic = new byte[TOKEN_STORAGE_MAGIC.length]; |
| in.readFully(magic); |
| if (!Arrays.equals(magic, TOKEN_STORAGE_MAGIC)) { |
| throw new IOException("Bad header found in token storage."); |
| } |
| byte version = in.readByte(); |
| if (version != TOKEN_STORAGE_VERSION && |
| version != OLD_TOKEN_STORAGE_VERSION) { |
| throw new IOException("Unknown version " + version + |
| " in token storage."); |
| } |
| if (version == OLD_TOKEN_STORAGE_VERSION) { |
| readFields(in); |
| } else if (version == TOKEN_STORAGE_VERSION) { |
| readProto(in); |
| } |
| } |
| |
| private static final byte[] TOKEN_STORAGE_MAGIC = |
| "HDTS".getBytes(Charsets.UTF_8); |
| private static final byte TOKEN_STORAGE_VERSION = 1; |
| |
| /** |
| * For backward compatibility. |
| */ |
| private static final byte OLD_TOKEN_STORAGE_VERSION = 0; |
| |
| |
| public void writeTokenStorageToStream(DataOutputStream os) |
| throws IOException { |
| os.write(TOKEN_STORAGE_MAGIC); |
| os.write(TOKEN_STORAGE_VERSION); |
| writeProto(os); |
| } |
| |
| public void writeTokenStorageFile(Path filename, |
| Configuration conf) throws IOException { |
| FSDataOutputStream os = filename.getFileSystem(conf).create(filename); |
| writeTokenStorageToStream(os); |
| os.close(); |
| } |
| |
| /** |
| * For backward compatibility. |
| */ |
| public void writeLegacyTokenStorageLocalFile(File f) throws IOException { |
| writeLegacyOutputStream(new DataOutputStream(new FileOutputStream(f))); |
| } |
| |
| /** |
| * For backward compatibility. |
| */ |
| public void writeLegacyTokenStorageFile(Path filename, Configuration conf) |
| throws IOException { |
| writeLegacyOutputStream(filename.getFileSystem(conf).create(filename)); |
| } |
| |
| private void writeLegacyOutputStream(DataOutputStream os) throws IOException { |
| os.write(TOKEN_STORAGE_MAGIC); |
| os.write(OLD_TOKEN_STORAGE_VERSION); |
| write(os); |
| os.close(); |
| } |
| |
| /** |
| * Stores all the keys to DataOutput. |
| * @param out |
| * @throws IOException |
| */ |
| @Override |
| public void write(DataOutput out) throws IOException { |
| // write out tokens first |
| WritableUtils.writeVInt(out, tokenMap.size()); |
| for(Map.Entry<Text, |
| Token<? extends TokenIdentifier>> e: tokenMap.entrySet()) { |
| e.getKey().write(out); |
| e.getValue().write(out); |
| } |
| |
| // now write out secret keys |
| WritableUtils.writeVInt(out, secretKeysMap.size()); |
| for(Map.Entry<Text, byte[]> e : secretKeysMap.entrySet()) { |
| e.getKey().write(out); |
| WritableUtils.writeVInt(out, e.getValue().length); |
| out.write(e.getValue()); |
| } |
| } |
| |
| /** |
| * Write contents of this instance as CredentialsProto message to DataOutput. |
| * @param out |
| * @throws IOException |
| */ |
| public void writeProto(DataOutput out) throws IOException { |
| CredentialsProto.Builder storage = CredentialsProto.newBuilder(); |
| for (Map.Entry<Text, Token<? extends TokenIdentifier>> e : |
| tokenMap.entrySet()) { |
| CredentialsKVProto.Builder kv = CredentialsKVProto.newBuilder(). |
| setAliasBytes(ByteString.copyFrom( |
| e.getKey().getBytes(), 0, e.getKey().getLength())). |
| setToken(e.getValue().toTokenProto()); |
| storage.addTokens(kv.build()); |
| } |
| |
| for(Map.Entry<Text, byte[]> e : secretKeysMap.entrySet()) { |
| CredentialsKVProto.Builder kv = CredentialsKVProto.newBuilder(). |
| setAliasBytes(ByteString.copyFrom( |
| e.getKey().getBytes(), 0, e.getKey().getLength())). |
| setSecret(ByteString.copyFrom(e.getValue())); |
| storage.addSecrets(kv.build()); |
| } |
| storage.build().writeDelimitedTo((DataOutputStream)out); |
| } |
| |
| /** |
| * Populates keys/values from proto buffer storage. |
| * @param in - stream ready to read a serialized proto buffer message |
| */ |
| public void readProto(DataInput in) throws IOException { |
| CredentialsProto storage = CredentialsProto.parseDelimitedFrom((DataInputStream)in); |
| for (CredentialsKVProto kv : storage.getTokensList()) { |
| addToken(new Text(kv.getAliasBytes().toByteArray()), |
| (Token<? extends TokenIdentifier>) new Token(kv.getToken())); |
| } |
| for (CredentialsKVProto kv : storage.getSecretsList()) { |
| addSecretKey(new Text(kv.getAliasBytes().toByteArray()), |
| kv.getSecret().toByteArray()); |
| } |
| } |
| |
| /** |
| * Loads all the keys. |
| * @param in |
| * @throws IOException |
| */ |
| @Override |
| public void readFields(DataInput in) throws IOException { |
| secretKeysMap.clear(); |
| tokenMap.clear(); |
| |
| int size = WritableUtils.readVInt(in); |
| for(int i=0; i<size; i++) { |
| Text alias = new Text(); |
| alias.readFields(in); |
| Token<? extends TokenIdentifier> t = new Token<TokenIdentifier>(); |
| t.readFields(in); |
| tokenMap.put(alias, t); |
| } |
| |
| size = WritableUtils.readVInt(in); |
| for(int i=0; i<size; i++) { |
| Text alias = new Text(); |
| alias.readFields(in); |
| int len = WritableUtils.readVInt(in); |
| byte[] value = new byte[len]; |
| in.readFully(value); |
| secretKeysMap.put(alias, value); |
| } |
| } |
| |
| /** |
| * Copy all of the credentials from one credential object into another. |
| * Existing secrets and tokens are overwritten. |
| * @param other the credentials to copy |
| */ |
| public void addAll(Credentials other) { |
| addAll(other, true); |
| } |
| |
| /** |
| * Copy all of the credentials from one credential object into another. |
| * Existing secrets and tokens are not overwritten. |
| * @param other the credentials to copy |
| */ |
| public void mergeAll(Credentials other) { |
| addAll(other, false); |
| } |
| |
| private void addAll(Credentials other, boolean overwrite) { |
| for(Map.Entry<Text, byte[]> secret: other.secretKeysMap.entrySet()) { |
| Text key = secret.getKey(); |
| if (!secretKeysMap.containsKey(key) || overwrite) { |
| secretKeysMap.put(key, secret.getValue()); |
| } |
| } |
| for(Map.Entry<Text, Token<?>> token: other.tokenMap.entrySet()){ |
| Text key = token.getKey(); |
| if (!tokenMap.containsKey(key) || overwrite) { |
| addToken(key, token.getValue()); |
| } |
| } |
| } |
| } |