Java tutorial
/** * 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.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A class that provides the facilities of reading and writing * secret keys and Tokens. */ @InterfaceAudience.Public @InterfaceStability.Evolving public class Credentials implements Writable { public enum SerializedFormat { WRITABLE((byte) 0x00), PROTOBUF((byte) 0x01); // Caching to avoid reconstructing the array each time. private static final SerializedFormat[] FORMATS = values(); final byte value; SerializedFormat(byte val) { this.value = val; } public static SerializedFormat valueOf(int val) { try { return FORMATS[val]; } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("Unknown credential format: " + val); } } } private static final Logger LOG = LoggerFactory.getLogger(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.isPrivateCloneOf(alias)) { tokensToAdd.put(e.getKey(), t.privateClone(token.getService())); } } 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 IOUtils.wrapException(filename.toString(), "Credentials" + ".readTokenStorageFile", ioe); } finally { IOUtils.cleanupWithLogger(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.cleanupWithLogger(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."); } SerializedFormat format; try { format = SerializedFormat.valueOf(in.readByte()); } catch (IllegalArgumentException e) { throw new IOException(e); } switch (format) { case WRITABLE: readFields(in); break; case PROTOBUF: readProto(in); break; default: throw new IOException("Unsupported format " + format); } } private static final byte[] TOKEN_STORAGE_MAGIC = "HDTS".getBytes(StandardCharsets.UTF_8); public void writeTokenStorageToStream(DataOutputStream os) throws IOException { // by default store in the oldest supported format for compatibility writeTokenStorageToStream(os, SerializedFormat.WRITABLE); } public void writeTokenStorageToStream(DataOutputStream os, SerializedFormat format) throws IOException { switch (format) { case WRITABLE: writeWritableOutputStream(os); break; case PROTOBUF: writeProtobufOutputStream(os); break; default: throw new IllegalArgumentException("Unsupported serialized format: " + format); } } private void writeWritableOutputStream(DataOutputStream os) throws IOException { os.write(TOKEN_STORAGE_MAGIC); os.write(SerializedFormat.WRITABLE.value); write(os); } private void writeProtobufOutputStream(DataOutputStream os) throws IOException { os.write(TOKEN_STORAGE_MAGIC); os.write(SerializedFormat.PROTOBUF.value); writeProto(os); } public void writeTokenStorageFile(Path filename, Configuration conf) throws IOException { // by default store in the oldest supported format for compatibility writeTokenStorageFile(filename, conf, SerializedFormat.WRITABLE); } public void writeTokenStorageFile(Path filename, Configuration conf, SerializedFormat format) throws IOException { try (FSDataOutputStream os = filename.getFileSystem(conf).create(filename)) { writeTokenStorageToStream(os, format); } } /** * 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 */ 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 */ 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()); } } } }