.../security/visibility/VisibilityConstants.java | 6 + .../main/java/org/apache/hadoop/hbase/TagType.java | 2 + .../DefaultVisibilityExpressionResolver.java | 9 +- .../replication/VisibilityReplicationEndpoint.java | 140 ++++++++ .../DefaultVisibilityLabelServiceImpl.java | 88 ++++- .../security/visibility/VisibilityController.java | 116 +++++-- .../security/visibility/VisibilityLabelFilter.java | 4 +- .../visibility/VisibilityLabelOrdinalProvider.java | 7 + .../visibility/VisibilityLabelService.java | 45 ++- .../security/visibility/VisibilityLabelsCache.java | 3 +- .../hbase/security/visibility/VisibilityUtils.java | 36 +- .../ExpAsStringVisibilityLabelServiceImpl.java | 43 ++- ...tVisibilityLabelReplicationWithExpAsString.java | 190 +++++++++++ .../TestVisibilityLabelsReplication.java | 366 +++++++++++++++++++++ 14 files changed, 1015 insertions(+), 40 deletions(-) diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityConstants.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityConstants.java index c51f401..570c203 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityConstants.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityConstants.java @@ -52,4 +52,10 @@ public final class VisibilityConstants { public static final String CHECK_AUTHS_FOR_MUTATION = "hbase.security.visibility.mutations.checkauths"; + public static final String NOT_OPERATOR = "!"; + public static final String AND_OPERATOR = "&"; + public static final String OR_OPERATOR = "|"; + public static final String OPEN_PARAN = "("; + public static final String CLOSED_PARAN = ")"; + } diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/TagType.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/TagType.java index 45c8476..f34906e 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/TagType.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/TagType.java @@ -28,4 +28,6 @@ public final class TagType { public static final byte VISIBILITY_TAG_TYPE = (byte) 2; public static final byte LOG_REPLAY_TAG_TYPE = (byte) 3; public static final byte VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE = (byte)4; + // String based tag type used in replication + public static final byte STRING_VIS_TAG_TYPE = (byte) 9; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/DefaultVisibilityExpressionResolver.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/DefaultVisibilityExpressionResolver.java index 150bb25..64f7d7d 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/DefaultVisibilityExpressionResolver.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/mapreduce/DefaultVisibilityExpressionResolver.java @@ -28,10 +28,10 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.Tag; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; @@ -114,6 +114,13 @@ public class DefaultVisibilityExpressionResolver implements VisibilityExpression public int getLabelOrdinal(String label) { return labels.get(label); } + + @Override + public String getLabel(int ordinal) { + // Unused + throw new UnsupportedOperationException( + "getLabel should not be used in VisibilityExpressionResolver"); + } }; return VisibilityUtils.createVisibilityExpTags(visExpression, true, false, null, provider); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/VisibilityReplicationEndpoint.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/VisibilityReplicationEndpoint.java new file mode 100644 index 0000000..11e0477 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/replication/VisibilityReplicationEndpoint.java @@ -0,0 +1,140 @@ +/** + * 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.hbase.replication; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.Tag; +import org.apache.hadoop.hbase.TagRewriteCell; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.regionserver.wal.HLog.Entry; +import org.apache.hadoop.hbase.regionserver.wal.WALEdit; +import org.apache.hadoop.hbase.security.visibility.VisibilityLabelService; +import org.apache.hadoop.hbase.security.visibility.VisibilityUtils; + +import com.google.common.util.concurrent.ListenableFuture; + +@InterfaceAudience.Private +public class VisibilityReplicationEndpoint implements ReplicationEndpoint { + + private static final Log LOG = LogFactory.getLog(VisibilityReplicationEndpoint.class); + // Leaking to this package + private ReplicationEndpoint delegator; + private VisibilityLabelService visibilityLabelsService; + + public VisibilityReplicationEndpoint(ReplicationEndpoint endpoint, + VisibilityLabelService visibilityLabelsService) { + this.delegator = endpoint; + this.visibilityLabelsService = visibilityLabelsService; + } + + @Override + public void init(Context context) throws IOException { + delegator.init(context); + } + + @Override + public boolean replicate(ReplicateContext replicateContext) { + try { + List entries = replicateContext.getEntries(); + List newEntries = new ArrayList(entries.size()); + for (Entry entry : entries) { + WALEdit newEdit = new WALEdit(); + ArrayList cells = entry.getEdit().getCells(); + for (Cell cell : cells) { + if (cell.getTagsLength() > 0) { + List visTags = new ArrayList(); + List nonVisTags = new ArrayList(); + Byte serializationFormat = VisibilityUtils.extractVisibilityAndNonVisibilityTags(cell, + visTags, nonVisTags); + List modifiedTags = visibilityLabelsService + .modifyTags(visTags, serializationFormat); + if (modifiedTags != null) { + nonVisTags.addAll(modifiedTags); + } + // Recreate the cell with the new tags and the existing tags + Cell newCell = new TagRewriteCell(cell, Tag.fromList(nonVisTags)); + newEdit.add(newCell); + } else { + newEdit.add(cell); + } + } + newEntries.add(new Entry(entry.getKey(), newEdit)); + } + replicateContext.setEntries(newEntries); + return delegator.replicate(replicateContext); + } catch (IOException ioe) { + LOG.error("Exception while reading the visibility labels from the cell", ioe); + // TODO: + // throw run time exception? or just return the old entries? + return false; + } + } + + @Override + public synchronized UUID getPeerUUID() { + return delegator.getPeerUUID(); + } + + @Override + public boolean canReplicateToSameCluster() { + return delegator.canReplicateToSameCluster(); + } + + @Override + public WALEntryFilter getWALEntryfilter() { + return delegator.getWALEntryfilter(); + } + + @Override + public boolean isRunning() { + return delegator.isRunning(); + } + + @Override + public ListenableFuture start() { + return delegator.start(); + } + + @Override + public State startAndWait() { + return delegator.startAndWait(); + } + + @Override + public State state() { + return delegator.state(); + } + + @Override + public ListenableFuture stop() { + return delegator.stop(); + } + + @Override + public State stopAndWait() { + return delegator.stopAndWait(); + } + +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/DefaultVisibilityLabelServiceImpl.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/DefaultVisibilityLabelServiceImpl.java index e94760e..6da7aa0 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/DefaultVisibilityLabelServiceImpl.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/DefaultVisibilityLabelServiceImpl.java @@ -39,12 +39,13 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.HConstants.OperationStatusCode; import org.apache.hadoop.hbase.Tag; +import org.apache.hadoop.hbase.TagType; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.client.Put; @@ -598,4 +599,89 @@ public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService } return matchFound; } + + @Override + public List modifyTags(List tags, Byte serializationFormat) throws IOException { + List existingTags = new ArrayList(); + if (serializationFormat == null || serializationFormat == SORTED_ORDINAL_SERIALIZATION_FORMAT) { + createModifiedVisTags(existingTags, tags); + return existingTags; + } else { + return tags; + } + } + + private List createModifiedVisTags(List existingTags, List tags) + throws IOException { + String visibilityString = ""; + boolean firstExpression = true; + for (Tag tag : tags) { + if (tag.getType() == TagType.VISIBILITY_TAG_TYPE) { + if (!firstExpression) { + visibilityString += VisibilityConstants.CLOSED_PARAN + VisibilityConstants.OR_OPERATOR; + } + int offset = tag.getTagOffset(); + int endOffset = offset + tag.getTagLength(); + boolean expressionStart = true; + while (offset < endOffset) { + Pair result = StreamUtils.readRawVarint32(tag.getBuffer(), offset); + int currLabelOrdinal = result.getFirst(); + if (currLabelOrdinal < 0) { + int temp = -currLabelOrdinal; + String label = this.labelsCache.getLabel(temp); + if (expressionStart) { + visibilityString += VisibilityConstants.OPEN_PARAN + VisibilityConstants.NOT_OPERATOR + + label; + } else { + visibilityString += VisibilityConstants.AND_OPERATOR + + VisibilityConstants.NOT_OPERATOR + label; + } + } else { + String label = this.labelsCache.getLabel(currLabelOrdinal); + if (expressionStart) { + visibilityString += VisibilityConstants.OPEN_PARAN + label; + } else { + visibilityString += VisibilityConstants.AND_OPERATOR + label; + } + } + firstExpression = false; + expressionStart = false; + offset += result.getSecond(); + } + } else if (tag.getType() != TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE) { + // Avoiding all tags related to visibility expression as ordinal + existingTags.add(tag); + } + } + if (visibilityString.length() != 0) { + visibilityString += VisibilityConstants.CLOSED_PARAN; + // Storing only string based serialization + Tag stringTag = new Tag(TagType.STRING_VIS_TAG_TYPE, Bytes.toBytes(visibilityString)); + existingTags.add(stringTag); + } + return existingTags; + } + + @Override + public Tag getModifiedTag(List tags) throws IOException { + Tag modifiedTag = null; + for (Tag tag : tags) { + if (tag.getType() == TagType.STRING_VIS_TAG_TYPE) { + modifiedTag = tag; + break; + } + } + return modifiedTag; + } + + @Override + public List removeModifiedTags(List tags) throws IOException { + List rewriteTags = new ArrayList(); + for (Tag tag : tags) { + if (tag.getType() != TagType.STRING_VIS_TAG_TYPE) { + rewriteTags.add(tag); + } + } + return rewriteTags; + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java index aaad8ba..d73f866 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityController.java @@ -32,7 +32,6 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellScanner; @@ -65,6 +64,7 @@ import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; import org.apache.hadoop.hbase.coprocessor.ObserverContext; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment; +import org.apache.hadoop.hbase.coprocessor.RegionServerObserver; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.exceptions.FailedSanityCheckException; import org.apache.hadoop.hbase.filter.Filter; @@ -91,12 +91,17 @@ import org.apache.hadoop.hbase.regionserver.InternalScanner; import org.apache.hadoop.hbase.regionserver.MiniBatchOperationInProgress; import org.apache.hadoop.hbase.regionserver.OperationStatus; import org.apache.hadoop.hbase.regionserver.RegionScanner; +import org.apache.hadoop.hbase.replication.ReplicationEndpoint; +import org.apache.hadoop.hbase.replication.VisibilityReplicationEndpoint; import org.apache.hadoop.hbase.security.AccessDeniedException; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.security.access.AccessControlLists; import org.apache.hadoop.hbase.security.access.AccessController; import org.apache.hadoop.hbase.util.ByteStringer; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.Pair; +import org.apache.hadoop.hbase.MetaMutationAnnotation; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; @@ -111,7 +116,7 @@ import com.google.protobuf.Service; */ @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) public class VisibilityController extends BaseMasterAndRegionObserver implements - VisibilityLabelsService.Interface, CoprocessorService { + RegionServerObserver, VisibilityLabelsService.Interface, CoprocessorService { private static final Log LOG = LogFactory.getLog(VisibilityController.class); // flags if we are running on a region of the 'labels' table @@ -143,17 +148,9 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements + " is required to persist visibility labels. Consider setting " + HFile.FORMAT_VERSION_KEY + " accordingly."); } - if (env instanceof RegionServerCoprocessorEnvironment) { - throw new RuntimeException( - "Visibility controller should not be configured as " + - "'hbase.coprocessor.regionserver.classes'."); - } - if (env instanceof RegionCoprocessorEnvironment) { - // VisibilityLabelService to be instantiated only with Region Observer. - visibilityLabelService = VisibilityLabelServiceManager.getInstance() - .getVisibilityLabelService(this.conf); - } + visibilityLabelService = VisibilityLabelServiceManager.getInstance().getVisibilityLabelService( + this.conf); this.superUsers = getSystemAndSuperUsers(); } @@ -281,12 +278,24 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements continue; } boolean sanityFailure = false; + Tag modifiedTag = null; for (CellScanner cellScanner = m.cellScanner(); cellScanner.advance();) { - if (!checkForReservedVisibilityTagPresence(cellScanner.current())) { + Pair pair = checkForReservedVisibilityTagPresence(cellScanner.current()); + if (!pair.getFirst()) { miniBatchOp.setOperationStatus(i, new OperationStatus(SANITY_CHECK_FAILURE, "Mutation contains cell with reserved type tag")); sanityFailure = true; break; + } else { + // Indicates that the cell has a the tag which was modified in the src replication cluster + if(pair.getSecond() != null) { + modifiedTag = pair.getSecond(); + String tag = Bytes.toString(modifiedTag.getValue()); + if(cellVisibility == null && tag != null) { + // May need to store only the first one + cellVisibility = new CellVisibility(tag); + } + } } } if (!sanityFailure) { @@ -313,6 +322,12 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements Cell cell = cellScanner.current(); List tags = Tag.asList(cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength()); + if (modifiedTag != null) { + // Rewrite the tags by removing the modified tags. Again depends on the + // VLS impl as how to remove the replication related tags + List rewriteTags = this.visibilityLabelService.removeModifiedTags(tags); + tags = rewriteTags; + } tags.addAll(visibilityTags); Cell updatedCell = new TagRewriteCell(cell, Tag.fromList(tags)); updatedCells.add(updatedCell); @@ -382,23 +397,34 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements // Checks whether cell contains any tag with type as VISIBILITY_TAG_TYPE. // This tag type is reserved and should not be explicitly set by user. - private boolean checkForReservedVisibilityTagPresence(Cell cell) throws IOException { + private Pair checkForReservedVisibilityTagPresence(Cell cell) throws IOException { // Bypass this check when the operation is done by a system/super user. // This is done because, while Replication, the Cells coming to the peer cluster with reserved // typed tags and this is fine and should get added to the peer cluster table + Pair pair = new Pair(false, null); if (isSystemOrSuperUser()) { - return true; + // Does the cell contain special tag which indicates that the replicated + // cell visiblilty tags + // have been modified + List tags = + Tag.asList(cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength()); + Tag modifiedTag = this.visibilityLabelService.getModifiedTag(tags); + pair.setFirst(true); + pair.setSecond(modifiedTag); + return pair; } if (cell.getTagsLength() > 0) { Iterator tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength()); while (tagsItr.hasNext()) { if (RESERVED_VIS_TAG_TYPES.contains(tagsItr.next().getType())) { - return false; + return pair; } } } - return true; + pair.setFirst(true); + pair.setSecond(null); + return pair; } @Override @@ -557,7 +583,7 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements public Result preAppend(ObserverContext e, Append append) throws IOException { for (CellScanner cellScanner = append.cellScanner(); cellScanner.advance();) { - if (!checkForReservedVisibilityTagPresence(cellScanner.current())) { + if (!checkForReservedVisibilityTagPresence(cellScanner.current()).getFirst()) { throw new FailedSanityCheckException("Append contains cell with reserved type tag"); } } @@ -568,7 +594,7 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements public Result preIncrement(ObserverContext e, Increment increment) throws IOException { for (CellScanner cellScanner = increment.cellScanner(); cellScanner.advance();) { - if (!checkForReservedVisibilityTagPresence(cellScanner.current())) { + if (!checkForReservedVisibilityTagPresence(cellScanner.current()).getFirst()) { throw new FailedSanityCheckException("Increment contains cell with reserved type tag"); } } @@ -817,4 +843,56 @@ public class VisibilityController extends BaseMasterAndRegionObserver implements return matchFound ? ReturnCode.INCLUDE : ReturnCode.SKIP; } } + + @Override + public void preStopRegionServer(ObserverContext env) + throws IOException { + } + + @Override + public void preMerge(ObserverContext ctx, HRegion regionA, + HRegion regionB) throws IOException { + } + + @Override + public void postMerge(ObserverContext c, HRegion regionA, + HRegion regionB, HRegion mergedRegion) throws IOException { + } + + @Override + public void preMergeCommit(ObserverContext ctx, + HRegion regionA, HRegion regionB, @MetaMutationAnnotation List metaEntries) + throws IOException { + } + + @Override + public void postMergeCommit(ObserverContext ctx, + HRegion regionA, HRegion regionB, HRegion mergedRegion) throws IOException { + } + + @Override + public void preRollBackMerge(ObserverContext ctx, + HRegion regionA, HRegion regionB) throws IOException { + } + + @Override + public void postRollBackMerge(ObserverContext ctx, + HRegion regionA, HRegion regionB) throws IOException { + } + + @Override + public void preRollWALWriterRequest(ObserverContext ctx) + throws IOException { + } + + @Override + public void postRollWALWriterRequest(ObserverContext ctx) + throws IOException { + } + + @Override + public ReplicationEndpoint postCreateReplicationEndPoint( + ObserverContext ctx, ReplicationEndpoint endpoint) { + return new VisibilityReplicationEndpoint(endpoint, visibilityLabelService); + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelFilter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelFilter.java index eb8abbe..30951da 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelFilter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelFilter.java @@ -20,8 +20,8 @@ package org.apache.hadoop.hbase.security.visibility; import java.io.IOException; import java.util.Map; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.filter.FilterBase; import org.apache.hadoop.hbase.util.ByteRange; import org.apache.hadoop.hbase.util.Bytes; @@ -33,7 +33,6 @@ import org.apache.hadoop.hbase.util.SimpleMutableByteRange; */ @InterfaceAudience.Private class VisibilityLabelFilter extends FilterBase { - private final VisibilityExpEvaluator expEvaluator; private final Map cfVsMaxVersions; private final ByteRange curFamily; @@ -74,7 +73,6 @@ class VisibilityLabelFilter extends FilterBase { if (curQualMetVersions > curFamilyMaxVersions) { return ReturnCode.SKIP; } - return this.expEvaluator.evaluate(cell) ? ReturnCode.INCLUDE : ReturnCode.SKIP; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelOrdinalProvider.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelOrdinalProvider.java index 81be70b..c2f6ef4 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelOrdinalProvider.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelOrdinalProvider.java @@ -28,4 +28,11 @@ public interface VisibilityLabelOrdinalProvider { * existing label. */ public int getLabelOrdinal(String label); + + /** + * Returns the string associated with the ordinal. Not be used in MR. + * @param ordinal + * @return label associated with the string, null if not found + */ + public String getLabel(int ordinal); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelService.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelService.java index 7f4c2ca..0b67eed 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelService.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelService.java @@ -20,12 +20,15 @@ package org.apache.hadoop.hbase.security.visibility; import java.io.IOException; import java.util.List; -import org.apache.hadoop.hbase.classification.InterfaceAudience; -import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.conf.Configurable; import org.apache.hadoop.hbase.Tag; +import org.apache.hadoop.hbase.TagType; +import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; import org.apache.hadoop.hbase.regionserver.OperationStatus; +import org.apache.hadoop.hbase.replication.ReplicationEndpoint; +import org.apache.hadoop.hbase.replication.VisibilityReplicationEndpoint; /** * The interface which deals with visibility labels and user auths admin service as well as the cell @@ -139,4 +142,42 @@ public interface VisibilityLabelService extends Configurable { */ boolean matchVisibility(List putVisTags, Byte putVisTagFormat, List deleteVisTags, Byte deleteVisTagFormat) throws IOException; + + /** + * Provides a way to modify the visibility tags of type + * {@link TagType}.VISIBILITY_TAG_TYPE, that are part of the cell created from + * the WALEdits that are prepared for replication while calling + * {@link ReplicationEndpoint} .replicate(). + * {@link VisibilityReplicationEndpoint} calls this API to provide an + * opportunity to modify the visibility tags before replicating. + * + * @param visTags + * the tags associated with the cell + * @param serailizationFormat + * the serialization format associated with the tag + * @return the modified list of visibility tags along with other existing + * tags, return the original tags if nothing to be modified + * @throws IOException + */ + List modifyTags(List visTags, Byte serializationFormat) throws IOException; + + /** + * Gets the modified tag, if found, base do on the + * {@link VisibilityLabelService} implementation. + * + * @param tags - list of tags in the cell + * @return - the modified tag + * @throws IOException + */ + Tag getModifiedTag(List tags) throws IOException; + + /** + * Provides a way to remove the modified tags. This may be helpful when + * the impl feels that the modified tags need not be serialized along with the + * cells + * @param tags - list of tags in the cell + * @return - list of tags after removal, if any + * @throws IOException + */ + List removeModifiedTags(List tags) throws IOException; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelsCache.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelsCache.java index 693134b..a5c2155 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelsCache.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityLabelsCache.java @@ -29,8 +29,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.MultiUserAuthorizations; import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.UserAuthorizations; @@ -174,6 +174,7 @@ public class VisibilityLabelsCache implements VisibilityLabelOrdinalProvider { * @return The label having the given ordinal. Returns null when no label exist in * the system with given ordinal */ + @Override public String getLabel(int ordinal) { this.lock.readLock().lock(); try { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityUtils.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityUtils.java index 96185dd..59bfa1e 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityUtils.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/visibility/VisibilityUtils.java @@ -28,19 +28,19 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.Tag; import org.apache.hadoop.hbase.TagType; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.filter.Filter; import org.apache.hadoop.hbase.io.util.StreamUtils; @@ -219,6 +219,38 @@ public class VisibilityUtils { return serializationFormat; } + /** + * Extracts the visibility tags and nonVisibility Tags + * + * @param cell + * @param visTags + * - all the visibilty tags of type TagType.VISIBILITY_TAG_TYPE would + * be added to this list + * @param nonVisTags - all the non visibility tags would be added to this list + * @return - the serailization format of the tag + */ + public static Byte extractVisibilityAndNonVisibilityTags(Cell cell, List visTags, + List nonVisTags) { + Byte serializationFormat = null; + if (cell.getTagsLength() > 0) { + Iterator tagsIterator = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(), + cell.getTagsLength()); + while (tagsIterator.hasNext()) { + Tag tag = tagsIterator.next(); + if (tag.getType() == TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE) { + serializationFormat = tag.getBuffer()[tag.getTagOffset()]; + } else if (tag.getType() == VISIBILITY_TAG_TYPE) { + visTags.add(tag); + } else { + // Just leave out the string format that will be added as part of + // modification + nonVisTags.add(tag); + } + } + } + return serializationFormat; + } + public static boolean isVisibilityTagsPresent(Cell cell) { if (cell.getTagsLength() == 0) { return false; diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/ExpAsStringVisibilityLabelServiceImpl.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/ExpAsStringVisibilityLabelServiceImpl.java index 997c17d..4398a64 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/ExpAsStringVisibilityLabelServiceImpl.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/ExpAsStringVisibilityLabelServiceImpl.java @@ -32,13 +32,13 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; -import org.apache.hadoop.hbase.TagType; import org.apache.hadoop.hbase.HConstants.OperationStatusCode; import org.apache.hadoop.hbase.Tag; +import org.apache.hadoop.hbase.TagType; +import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.HTable; @@ -59,9 +59,10 @@ import org.apache.hadoop.hbase.util.Bytes; import com.google.common.collect.Lists; /** - * This is a VisibilityLabelService where labels in Mutation's visibility expression will be - * persisted as Strings itself rather than ordinals in 'labels' table. Also there is no need to add - * labels to the system, prior to using them in Mutations/Authorizations. + * This is a VisibilityLabelService where labels in Mutation's visibility + * expression will be persisted as Strings itself rather than ordinals in + * 'labels' table. Also there is no need to add labels to the system, prior to + * using them in Mutations/Authorizations. */ @InterfaceAudience.Private public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelService { @@ -72,7 +73,7 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer private static final byte STRING_SERIALIZATION_FORMAT = 2; private static final Tag STRING_SERIALIZATION_FORMAT_TAG = new Tag( TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE, - new byte[]{STRING_SERIALIZATION_FORMAT}); + new byte[] { STRING_SERIALIZATION_FORMAT }); private final ExpressionParser expressionParser = new ExpressionParser(); private final ExpressionExpander expressionExpander = new ExpressionExpander(); private Configuration conf; @@ -81,7 +82,8 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer @Override public OperationStatus[] addLabels(List labels) throws IOException { - // Not doing specific label add. We will just add labels in Mutation visibility expression as it + // Not doing specific label add. We will just add labels in Mutation + // visibility expression as it // is along with every cell. OperationStatus[] status = new OperationStatus[labels.size()]; for (int i = 0; i < labels.size(); i++) { @@ -252,7 +254,8 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer offset += len; } if (includeKV) { - // We got one visibility expression getting evaluated to true. Good to include this + // We got one visibility expression getting evaluated to true. + // Good to include this // KV in the result then. return true; } @@ -278,7 +281,8 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer Collections.sort(labels); Collections.sort(notLabels); // We will write the NOT labels 1st followed by normal labels - // Each of the label we will write with label length (as short 1st) followed by the label bytes. + // Each of the label we will write with label length (as short 1st) followed + // by the label bytes. // For a NOT node we will write the label length as -ve. for (String label : notLabels) { byte[] bLabel = Bytes.toBytes(label); @@ -377,7 +381,8 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer private static boolean checkForMatchingVisibilityTagsWithSortedOrder(List putVisTags, List deleteVisTags) { boolean matchFound = false; - // If the size does not match. Definitely we are not comparing the equal tags. + // If the size does not match. Definitely we are not comparing the equal + // tags. if ((deleteVisTags.size()) == putVisTags.size()) { for (Tag tag : deleteVisTags) { matchFound = false; @@ -388,9 +393,25 @@ public class ExpAsStringVisibilityLabelServiceImpl implements VisibilityLabelSer break; } } - if (!matchFound) break; + if (!matchFound) + break; } } return matchFound; } + + @Override + public List modifyTags(List tags, Byte serializationFormat) throws IOException { + return tags; + } + + @Override + public Tag getModifiedTag(List tags) throws IOException { + return null; + } + + @Override + public List removeModifiedTags(List tags) throws IOException { + return null; + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestVisibilityLabelReplicationWithExpAsString.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestVisibilityLabelReplicationWithExpAsString.java new file mode 100644 index 0000000..c2aca3d --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestVisibilityLabelReplicationWithExpAsString.java @@ -0,0 +1,190 @@ +/** + * 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.hbase.security.visibility; + +import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.security.PrivilegedExceptionAction; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellScanner; +import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTable; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.replication.ReplicationAdmin; +import org.apache.hadoop.hbase.codec.KeyValueCodecWithTags; +import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.testclassification.SecurityTests; +import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster; +import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; +import org.junit.Before; +import org.junit.experimental.categories.Category; + +@Category({ SecurityTests.class, MediumTests.class }) +public class TestVisibilityLabelReplicationWithExpAsString extends TestVisibilityLabelsReplication { + private static final Log LOG = LogFactory + .getLog(TestVisibilityLabelReplicationWithExpAsString.class); + + @Override + @Before + public void setup() throws Exception { + expected[0] = 2; + expected[1] = 4; + expected[2] = 2; + expected[3] = 0; + // setup configuration + conf = HBaseConfiguration.create(); + conf.setBoolean(HConstants.DISTRIBUTED_LOG_REPLAY_KEY, false); + conf.setBoolean("hbase.online.schema.update.enable", true); + conf.setInt("hfile.format.version", 3); + conf.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/1"); + conf.setInt("replication.source.size.capacity", 10240); + conf.setLong("replication.source.sleepforretries", 100); + conf.setInt("hbase.regionserver.maxlogs", 10); + conf.setLong("hbase.master.logcleaner.ttl", 10); + conf.setInt("zookeeper.recovery.retry", 1); + conf.setInt("zookeeper.recovery.retry.intervalmill", 10); + conf.setBoolean("dfs.support.append", true); + conf.setLong(HConstants.THREAD_WAKE_FREQUENCY, 100); + conf.setInt("replication.stats.thread.period.seconds", 5); + conf.setBoolean("hbase.tests.use.shortcircuit.reads", false); + setVisibilityLabelServiceImpl(conf, ExpAsStringVisibilityLabelServiceImpl.class); + conf.setBoolean(HConstants.REPLICATION_ENABLE_KEY, HConstants.REPLICATION_ENABLE_DEFAULT); + conf.setStrings(HConstants.REPLICATION_CODEC_CONF_KEY, KeyValueCodecWithTags.class.getName()); + VisibilityTestUtil.enableVisiblityLabels(conf); + conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, + VisibilityController.class.getName()); + // Have to reset conf1 in case zk cluster location different + // than default + conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class, + ScanLabelGenerator.class); + conf.set("hbase.superuser", "admin"); + conf.set("hbase.superuser", User.getCurrent().getShortName()); + SUPERUSER = User.createUserForTesting(conf, User.getCurrent().getShortName(), + new String[] { "supergroup" }); + User.createUserForTesting(conf, User.getCurrent().getShortName(), new String[] { "supergroup" }); + USER1 = User.createUserForTesting(conf, "user1", new String[] {}); + TEST_UTIL = new HBaseTestingUtility(conf); + TEST_UTIL.startMiniZKCluster(); + MiniZooKeeperCluster miniZK = TEST_UTIL.getZkCluster(); + zkw1 = new ZooKeeperWatcher(conf, "cluster1", null, true); + replicationAdmin = new ReplicationAdmin(conf); + + // Base conf2 on conf1 so it gets the right zk cluster. + conf1 = HBaseConfiguration.create(conf); + conf1.setInt("hfile.format.version", 3); + conf1.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/2"); + conf1.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6); + conf1.setBoolean("dfs.support.append", true); + conf1.setBoolean("hbase.tests.use.shortcircuit.reads", false); + conf.setBoolean(HConstants.REPLICATION_ENABLE_KEY, HConstants.REPLICATION_ENABLE_DEFAULT); + conf1.setStrings(HConstants.REPLICATION_CODEC_CONF_KEY, KeyValueCodecWithTags.class.getName()); + conf1.setStrings(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY, + TestCoprocessorForTagsAtSink.class.getName()); + setVisibilityLabelServiceImpl(conf1, ExpAsStringVisibilityLabelServiceImpl.class); + TEST_UTIL1 = new HBaseTestingUtility(conf1); + TEST_UTIL1.setZkCluster(miniZK); + zkw2 = new ZooKeeperWatcher(conf1, "cluster2", null, true); + replicationAdmin.addPeer("2", TEST_UTIL1.getClusterKey()); + + TEST_UTIL.startMiniCluster(1); + // Wait for the labels table to become available + TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000); + TEST_UTIL1.startMiniCluster(1); + HBaseAdmin hBaseAdmin = TEST_UTIL.getHBaseAdmin(); + HTableDescriptor table = new HTableDescriptor(TableName.valueOf(TABLE_NAME)); + HColumnDescriptor desc = new HColumnDescriptor(fam); + desc.setScope(HConstants.REPLICATION_SCOPE_GLOBAL); + table.addFamily(desc); + try { + hBaseAdmin.createTable(table); + } finally { + if (hBaseAdmin != null) { + hBaseAdmin.close(); + } + } + HBaseAdmin hBaseAdmin1 = TEST_UTIL1.getHBaseAdmin(); + try { + hBaseAdmin1.createTable(table); + } finally { + if (hBaseAdmin1 != null) { + hBaseAdmin1.close(); + } + } + addLabels(); + setAuths(conf); + setAuths(conf1); + } + + protected static void setVisibilityLabelServiceImpl(Configuration conf, Class clazz) { + conf.setClass(VisibilityLabelServiceManager.VISIBILITY_LABEL_SERVICE_CLASS, + clazz, VisibilityLabelService.class); + } + + @Override + protected void verifyGet(final byte[] row, final String visStringTag, final int expected, + final boolean nullExpected, final String... auths) + throws IOException, InterruptedException { + PrivilegedExceptionAction scanAction = new PrivilegedExceptionAction() { + HTable table2 = null; + + public Void run() throws Exception { + try { + table2 = new HTable(conf1, TABLE_NAME_BYTES); + CellScanner cellScanner; + Cell current; + Get get = new Get(row); + get.setAuthorizations(new Authorizations(auths)); + Result result = table2.get(get); + cellScanner = result.cellScanner(); + boolean advance = cellScanner.advance(); + if (nullExpected) { + assertTrue(!advance); + return null; + } + current = cellScanner.current(); + assertArrayEquals(CellUtil.cloneRow(current), row); + assertEquals(expected, TestCoprocessorForTagsAtSink.tags.size()); + return null; + } finally { + if (table2 != null) { + table2.close(); + } + } + } + }; + USER1.runAs(scanAction); + } +} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestVisibilityLabelsReplication.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestVisibilityLabelsReplication.java new file mode 100644 index 0000000..e62d1b2 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/visibility/TestVisibilityLabelsReplication.java @@ -0,0 +1,366 @@ +/** + * 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.hbase.security.visibility; + +import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.CellScanner; +import org.apache.hadoop.hbase.CellUtil; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.Tag; +import org.apache.hadoop.hbase.TagType; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTable; +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.Scan; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.client.replication.ReplicationAdmin; +import org.apache.hadoop.hbase.codec.KeyValueCodecWithTags; +import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver; +import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; +import org.apache.hadoop.hbase.coprocessor.ObserverContext; +import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; +import org.apache.hadoop.hbase.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse; +import org.apache.hadoop.hbase.security.User; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.testclassification.SecurityTests; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster; +import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; + +@Category({ SecurityTests.class, MediumTests.class }) +public class TestVisibilityLabelsReplication { + private static final Log LOG = LogFactory.getLog(TestVisibilityLabelsReplication.class); + protected static Configuration conf; + protected static Configuration conf1; + protected static String TABLE_NAME = "TABLE_NAME"; + protected static byte[] TABLE_NAME_BYTES = Bytes.toBytes(TABLE_NAME); + protected static ReplicationAdmin replicationAdmin; + public static final String TOPSECRET = "topsecret"; + public static final String PUBLIC = "public"; + public static final String PRIVATE = "private"; + public static final String CONFIDENTIAL = "confidential"; + public static final String SECRET = "secret"; + public static HBaseTestingUtility TEST_UTIL; + public static HBaseTestingUtility TEST_UTIL1; + public static final byte[] row1 = Bytes.toBytes("row1"); + public static final byte[] row2 = Bytes.toBytes("row2"); + public static final byte[] row3 = Bytes.toBytes("row3"); + public static final byte[] row4 = Bytes.toBytes("row4"); + public final static byte[] fam = Bytes.toBytes("info"); + public final static byte[] qual = Bytes.toBytes("qual"); + public final static byte[] value = Bytes.toBytes("value"); + protected static ZooKeeperWatcher zkw1; + protected static ZooKeeperWatcher zkw2; + protected static int expected[] = { 3, 5, 3, 0 }; + + @Rule + public final TestName TEST_NAME = new TestName(); + public static User SUPERUSER, USER1; + + @Before + public void setup() throws Exception { + // setup configuration + conf = HBaseConfiguration.create(); + conf.setBoolean(HConstants.DISTRIBUTED_LOG_REPLAY_KEY, false); + conf.setBoolean("hbase.online.schema.update.enable", true); + conf.setInt("hfile.format.version", 3); + conf.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/1"); + conf.setInt("replication.source.size.capacity", 10240); + conf.setLong("replication.source.sleepforretries", 100); + conf.setInt("hbase.regionserver.maxlogs", 10); + conf.setLong("hbase.master.logcleaner.ttl", 10); + conf.setInt("zookeeper.recovery.retry", 1); + conf.setInt("zookeeper.recovery.retry.intervalmill", 10); + conf.setBoolean("dfs.support.append", true); + conf.setLong(HConstants.THREAD_WAKE_FREQUENCY, 100); + conf.setInt("replication.stats.thread.period.seconds", 5); + conf.setBoolean("hbase.tests.use.shortcircuit.reads", false); + setVisibilityLabelServiceImpl(conf); + conf.setBoolean(HConstants.REPLICATION_ENABLE_KEY, HConstants.REPLICATION_ENABLE_DEFAULT); + conf.setStrings(HConstants.REPLICATION_CODEC_CONF_KEY, KeyValueCodecWithTags.class.getName()); + VisibilityTestUtil.enableVisiblityLabels(conf); + conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, + VisibilityController.class.getName()); + // Have to reset conf1 in case zk cluster location different + // than default + conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class, + ScanLabelGenerator.class); + conf.set("hbase.superuser", User.getCurrent().getShortName()); + SUPERUSER = User.createUserForTesting(conf, User.getCurrent().getShortName(), + new String[] { "supergroup" }); + // User.createUserForTesting(conf, User.getCurrent().getShortName(), new + // String[] { "supergroup" }); + USER1 = User.createUserForTesting(conf, "user1", new String[] {}); + TEST_UTIL = new HBaseTestingUtility(conf); + TEST_UTIL.startMiniZKCluster(); + MiniZooKeeperCluster miniZK = TEST_UTIL.getZkCluster(); + zkw1 = new ZooKeeperWatcher(conf, "cluster1", null, true); + replicationAdmin = new ReplicationAdmin(conf); + + // Base conf2 on conf1 so it gets the right zk cluster. + conf1 = HBaseConfiguration.create(conf); + conf1.setInt("hfile.format.version", 3); + conf1.set(HConstants.ZOOKEEPER_ZNODE_PARENT, "/2"); + conf1.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6); + conf1.setBoolean("dfs.support.append", true); + conf1.setBoolean("hbase.tests.use.shortcircuit.reads", false); + conf.setBoolean(HConstants.REPLICATION_ENABLE_KEY, HConstants.REPLICATION_ENABLE_DEFAULT); + conf1.setStrings(HConstants.REPLICATION_CODEC_CONF_KEY, KeyValueCodecWithTags.class.getName()); + conf1.setStrings(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY, + TestCoprocessorForTagsAtSink.class.getName()); + // setVisibilityLabelServiceImpl(conf1); + USER1 = User.createUserForTesting(conf1, "user1", new String[] {}); + TEST_UTIL1 = new HBaseTestingUtility(conf1); + TEST_UTIL1.setZkCluster(miniZK); + zkw2 = new ZooKeeperWatcher(conf1, "cluster2", null, true); + replicationAdmin.addPeer("2", TEST_UTIL1.getClusterKey()); + + TEST_UTIL.startMiniCluster(1); + // Wait for the labels table to become available + TEST_UTIL.waitTableEnabled(LABELS_TABLE_NAME.getName(), 50000); + TEST_UTIL1.startMiniCluster(1); + HBaseAdmin hBaseAdmin = TEST_UTIL.getHBaseAdmin(); + HTableDescriptor table = new HTableDescriptor(TableName.valueOf(TABLE_NAME)); + HColumnDescriptor desc = new HColumnDescriptor(fam); + desc.setScope(HConstants.REPLICATION_SCOPE_GLOBAL); + table.addFamily(desc); + try { + hBaseAdmin.createTable(table); + } finally { + if (hBaseAdmin != null) { + hBaseAdmin.close(); + } + } + HBaseAdmin hBaseAdmin1 = TEST_UTIL1.getHBaseAdmin(); + try { + hBaseAdmin1.createTable(table); + } finally { + if (hBaseAdmin1 != null) { + hBaseAdmin1.close(); + } + } + addLabels(); + setAuths(conf); + setAuths(conf1); + } + + protected static void setVisibilityLabelServiceImpl(Configuration conf) { + conf.setClass(VisibilityLabelServiceManager.VISIBILITY_LABEL_SERVICE_CLASS, + DefaultVisibilityLabelServiceImpl.class, VisibilityLabelService.class); + } + + @Test + public void testVisibilityReplication() throws Exception { + TableName tableName = TableName.valueOf(TABLE_NAME); + Table table = writeData(tableName, "(" + SECRET + "&" + PUBLIC + ")" + "|(" + CONFIDENTIAL + + ")&(" + TOPSECRET + ")", "(" + PRIVATE + "|" + CONFIDENTIAL + ")&(" + PUBLIC + "|" + + TOPSECRET + ")", "(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET); + int retry = 0; + try { + Scan s = new Scan(); + s.setAuthorizations(new Authorizations(SECRET, CONFIDENTIAL, PRIVATE, TOPSECRET)); + ResultScanner scanner = table.getScanner(s); + Result[] next = scanner.next(3); + + assertTrue(next.length == 3); + CellScanner cellScanner = next[0].cellScanner(); + cellScanner.advance(); + Cell current = cellScanner.current(); + assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), + current.getRowLength(), row1, 0, row1.length)); + cellScanner = next[1].cellScanner(); + cellScanner.advance(); + current = cellScanner.current(); + assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), + current.getRowLength(), row2, 0, row2.length)); + cellScanner = next[2].cellScanner(); + cellScanner.advance(); + current = cellScanner.current(); + assertTrue(Bytes.equals(current.getRowArray(), current.getRowOffset(), + current.getRowLength(), row3, 0, row3.length)); + HTable table2 = null; + try { + table2 = new HTable(TEST_UTIL1.getConfiguration(), TABLE_NAME_BYTES); + s = new Scan(); + // Ensure both rows are replicated + scanner = table2.getScanner(s); + next = scanner.next(3); + while (next.length == 0 && retry <= 10) { + scanner = table2.getScanner(s); + next = scanner.next(3); + Thread.sleep(2000); + retry++; + } + assertTrue(next.length == 3); + verifyGet(row1, "(secret&topsecret&public)|(topsecret&confidential)", expected[0], false, + TOPSECRET, CONFIDENTIAL); + TestCoprocessorForTagsAtSink.tags.clear(); + verifyGet(row2, + "(public&private)|(topsecret&private)|(confidential&public)|(topsecret&confidential)", + expected[1], false, CONFIDENTIAL, PUBLIC); + TestCoprocessorForTagsAtSink.tags.clear(); + verifyGet(row3, "(!topsecret&secret)|(!topsecret&confidential)", expected[2], false, + PRIVATE, SECRET); + verifyGet(row3, "(!topsecret&secret)|(!topsecret&confidential)", expected[3], true, + TOPSECRET, SECRET); + } finally { + if (table2 != null) { + table2.close(); + } + } + } finally { + if (table != null) { + table.close(); + } + } + } + + protected void verifyGet(final byte[] row, final String visStringTag, final int expected, + final boolean nullExpected, final String... auths) throws IOException, InterruptedException { + PrivilegedExceptionAction scanAction = new PrivilegedExceptionAction() { + HTable table2 = null; + + public Void run() throws Exception { + try { + table2 = new HTable(conf1, TABLE_NAME_BYTES); + CellScanner cellScanner; + Cell current; + Get get = new Get(row); + get.setAuthorizations(new Authorizations(auths)); + Result result = table2.get(get); + cellScanner = result.cellScanner(); + boolean advance = cellScanner.advance(); + if (nullExpected) { + assertTrue(!advance); + return null; + } + current = cellScanner.current(); + assertArrayEquals(CellUtil.cloneRow(current), row); + for (Tag tag : TestCoprocessorForTagsAtSink.tags) { + System.out.println("The tag type is "+ tag.getType()); + } + assertEquals(expected, TestCoprocessorForTagsAtSink.tags.size()); + Tag tag = TestCoprocessorForTagsAtSink.tags.get(0); + assertEquals(TagType.VISIBILITY_EXP_SERIALIZATION_FORMAT_TAG_TYPE, tag.getType()); + return null; + } finally { + if (table2 != null) { + table2.close(); + } + } + } + }; + USER1.runAs(scanAction); + } + + public static void addLabels() throws Exception { + PrivilegedExceptionAction action = + new PrivilegedExceptionAction() { + public VisibilityLabelsResponse run() throws Exception { + String[] labels = { SECRET, TOPSECRET, CONFIDENTIAL, PUBLIC, PRIVATE }; + try { + VisibilityClient.addLabels(conf, labels); + } catch (Throwable t) { + throw new IOException(t); + } + return null; + } + }; + SUPERUSER.runAs(action); + } + + public static void setAuths(final Configuration conf) throws Exception { + PrivilegedExceptionAction action = + new PrivilegedExceptionAction() { + public VisibilityLabelsResponse run() throws Exception { + try { + return VisibilityClient.setAuths(conf, new String[] { SECRET, CONFIDENTIAL, PRIVATE, + TOPSECRET }, "user1"); + } catch (Throwable e) { + throw new Exception(e); + } + } + }; + VisibilityLabelsResponse response = SUPERUSER.runAs(action); + } + + static Table writeData(TableName tableName, String... labelExps) throws Exception { + HTable table = null; + try { + table = new HTable(conf, TABLE_NAME_BYTES); + int i = 1; + List puts = new ArrayList(); + for (String labelExp : labelExps) { + Put put = new Put(Bytes.toBytes("row" + i)); + put.add(fam, qual, HConstants.LATEST_TIMESTAMP, value); + put.setCellVisibility(new CellVisibility(labelExp)); + puts.add(put); + i++; + } + table.put(puts); + } finally { + if (table != null) { + table.flushCommits(); + } + } + return table; + } + + public static class TestCoprocessorForTagsAtSink extends BaseRegionObserver { + public static List tags = null; + + @Override + public void postGetOp(ObserverContext e, Get get, + List results) throws IOException { + if (results.size() > 0) { + // Check tag presence in the 1st cell in 1st Result + if (!results.isEmpty()) { + Cell cell = results.get(0); + tags = Tag.asList(cell.getTagsArray(), cell.getTagsOffset(), cell.getTagsLength()); + } + } + } + } + +}