package com.ngdata.lily.security.hbase.client; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.client.Append; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.HTableInterface; import org.apache.hadoop.hbase.client.Increment; import org.apache.hadoop.hbase.client.OperationWithAttributes; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Row; import org.apache.hadoop.hbase.client.RowLock; import org.apache.hadoop.hbase.client.RowMutations; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.client.coprocessor.Batch; import org.apache.hadoop.hbase.ipc.CoprocessorProtocol; import org.apache.hadoop.hbase.util.Bytes; import javax.annotation.Nullable; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; /** * An implementation of, and wrapper around, HTableInterface that automatically adds * the authentication context onto requests. This is just helper code to assure correct * communication of the user context on all requests. * * <p>Some methods don't support passing attributes, and for those we can't add authentication * info. These methods throw an exception. It is of course possible to bypass the authentication * by using the wrapped HTable instance, but then no authorization filtering will be applied!</p> * * <p>This method will modify the operation objects (the Get, Put, etc.) by adding an attribute * to them (cfr. HBase's OperationWithAttributes). This means it is not a good idea to have, say, * a Get object shared between threads.</p> */ public class AuthEnabledHTable implements HTableInterface { private HTableInterface delegate; private AuthorizationContextProvider authzCtxProvider; private byte[] extraPermissions; private byte[] appName; private boolean failWhenNotAuthenticated; private final String ERROR_MSG = "Method not supported with authentication"; /** * * @param failWhenNotAuthenticated throw an exception if there is no authorization context (= authenticated * user) available, i.o.w. refuse to do requests that will not be subject to * authorization filtering. * @param appName application name, identifies the permissions that should be active for this application. * Permissions start with "appName:...". * @param extraPermissions extra permissions which will be passed upon each request and which extend the * permissions of the user. See {@link HBaseAuthzUtil#EXTRA_PERMISSION_ATT}. This overwrites * any per-request permissions which might already have been set. */ public AuthEnabledHTable(AuthorizationContextProvider authzCtxProvider, boolean failWhenNotAuthenticated, String appName, @Nullable Set<String> extraPermissions, HTableInterface delegate) { this.authzCtxProvider = authzCtxProvider; this.appName = Bytes.toBytes(appName); this.extraPermissions = extraPermissions != null ? HBaseAuthzUtil.serialize(extraPermissions) : null; this.failWhenNotAuthenticated = failWhenNotAuthenticated; this.delegate = delegate; } private void addAuthInfo(OperationWithAttributes op) { addAuthInfo(Arrays.<OperationWithAttributes>asList(op)); } private void addAuthInfo(Iterable<? extends OperationWithAttributes> ops) { // For multi-ops: since the list of operations might be split to sent to different servers, we // need to add the authentication context to all of them. AuthorizationContext authzCtx = authzCtxProvider.getAuthorizationContext(); if (authzCtx == null && failWhenNotAuthenticated) { throw new RuntimeException("No authenticated user available."); } else if (authzCtx == null) { return; } byte[] userAsBytes = authzCtx.serialize(); for (OperationWithAttributes op : ops) { op.setAttribute(AuthorizationContext.OPERATION_ATTRIBUTE, userAsBytes); op.setAttribute(HBaseAuthzUtil.APP_NAME_ATT, appName); if (extraPermissions != null) { op.setAttribute(HBaseAuthzUtil.EXTRA_PERMISSION_ATT, extraPermissions); } } } @Override public byte[] getTableName() { return delegate.getTableName(); } @Override public Configuration getConfiguration() { return delegate.getConfiguration(); } @Override public HTableDescriptor getTableDescriptor() throws IOException { return delegate.getTableDescriptor(); } @Override public boolean exists(Get get) throws IOException { addAuthInfo(get); return delegate.exists(get); } @Override public void batch(List<? extends Row> actions, Object[] results) throws IOException, InterruptedException { throw new RuntimeException(ERROR_MSG); } @Override public Object[] batch(List<? extends Row> actions) throws IOException, InterruptedException { throw new RuntimeException(ERROR_MSG); } @Override public Result get(Get get) throws IOException { addAuthInfo(get); return delegate.get(get); } @Override public Result[] get(List<Get> gets) throws IOException { addAuthInfo(gets); return delegate.get(gets); } @Override public Result getRowOrBefore(byte[] row, byte[] family) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public ResultScanner getScanner(Scan scan) throws IOException { addAuthInfo(scan); return delegate.getScanner(scan); } @Override public ResultScanner getScanner(byte[] family) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public void put(Put put) throws IOException { addAuthInfo(put); delegate.put(put); } @Override public void put(List<Put> puts) throws IOException { addAuthInfo(puts); delegate.put(puts); } @Override public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) throws IOException { addAuthInfo(put); return delegate.checkAndPut(row, family, qualifier, value, put); } @Override public void delete(Delete delete) throws IOException { addAuthInfo(delete); delegate.delete(delete); } @Override public void delete(List<Delete> deletes) throws IOException { addAuthInfo(deletes); delegate.delete(deletes); } @Override public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value, Delete delete) throws IOException { addAuthInfo(delete); return delegate.checkAndDelete(row, family, qualifier, value, delete); } @Override public void mutateRow(RowMutations rm) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public Result append(Append append) throws IOException { addAuthInfo(append); return delegate.append(append); } @Override public Result increment(Increment increment) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public long incrementColumnValue(byte[] row, byte[] family, byte[] qualifier, long amount, boolean writeToWAL) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public boolean isAutoFlush() { return delegate.isAutoFlush(); } @Override public void flushCommits() throws IOException { delegate.flushCommits(); } @Override public void close() throws IOException { delegate.close(); } @Override public RowLock lockRow(byte[] row) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public void unlockRow(RowLock rl) throws IOException { throw new RuntimeException(ERROR_MSG); } @Override public <T extends CoprocessorProtocol> T coprocessorProxy(Class<T> protocol, byte[] row) { return delegate.coprocessorProxy(protocol, row); } @Override public <T extends CoprocessorProtocol, R> Map<byte[], R> coprocessorExec(Class<T> protocol, byte[] startKey, byte[] endKey, Batch.Call<T, R> callable) throws IOException, Throwable { return delegate.coprocessorExec(protocol, startKey, endKey, callable); } @Override public <T extends CoprocessorProtocol, R> void coprocessorExec(Class<T> protocol, byte[] startKey, byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback) throws IOException, Throwable { delegate.coprocessorExec(protocol, startKey, endKey, callable, callback); } @Override public void setAutoFlush(boolean autoFlush) { delegate.setAutoFlush(autoFlush); } @Override public void setAutoFlush(boolean autoFlush, boolean clearBufferOnFail) { delegate.setAutoFlush(autoFlush, clearBufferOnFail); } @Override public long getWriteBufferSize() { return delegate.getWriteBufferSize(); } @Override public void setWriteBufferSize(long writeBufferSize) throws IOException { delegate.setWriteBufferSize(writeBufferSize); } }