1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.hadoop.hbase.security.access;
20 
21 import java.io.IOException;
22 import java.util.Map;
23 
24 import org.apache.hadoop.hbase.classification.InterfaceAudience;
25 import org.apache.hadoop.hbase.Cell;
26 import org.apache.hadoop.hbase.CellUtil;
27 import org.apache.hadoop.hbase.TableName;
28 import org.apache.hadoop.hbase.exceptions.DeserializationException;
29 import org.apache.hadoop.hbase.filter.FilterBase;
30 import org.apache.hadoop.hbase.security.User;
31 import org.apache.hadoop.hbase.util.ByteRange;
32 import org.apache.hadoop.hbase.util.Bytes;
33 import org.apache.hadoop.hbase.util.SimpleMutableByteRange;
34 
35 /**
36  * <strong>NOTE: for internal use only by AccessController implementation</strong>
37  *
38  * <p>
39  * TODO: There is room for further performance optimization here.
40  * Calling TableAuthManager.authorize() per KeyValue imposes a fair amount of
41  * overhead.  A more optimized solution might look at the qualifiers where
42  * permissions are actually granted and explicitly limit the scan to those.
43  * </p>
44  * <p>
45  * We should aim to use this _only_ when access to the requested column families
46  * is not granted at the column family levels.  If table or column family
47  * access succeeds, then there is no need to impose the overhead of this filter.
48  * </p>
49  */
50 @InterfaceAudience.Private
51 class AccessControlFilter extends FilterBase {
52 
53   public static enum Strategy {
54     /** Filter only by checking the table or CF permissions */
55     CHECK_TABLE_AND_CF_ONLY,
56     /** Cell permissions can override table or CF permissions */
57     CHECK_CELL_DEFAULT,
58   };
59 
60   private TableAuthManager authManager;
61   private TableName table;
62   private User user;
63   private boolean isSystemTable;
64   private Strategy strategy;
65   private Map<ByteRange, Integer> cfVsMaxVersions;
66   private int familyMaxVersions;
67   private int currentVersions;
68   private ByteRange prevFam;
69   private ByteRange prevQual;
70 
71   /**
72    * For Writable
73    */
AccessControlFilter()74   AccessControlFilter() {
75   }
76 
AccessControlFilter(TableAuthManager mgr, User ugi, TableName tableName, Strategy strategy, Map<ByteRange, Integer> cfVsMaxVersions)77   AccessControlFilter(TableAuthManager mgr, User ugi, TableName tableName,
78       Strategy strategy, Map<ByteRange, Integer> cfVsMaxVersions) {
79     authManager = mgr;
80     table = tableName;
81     user = ugi;
82     isSystemTable = tableName.isSystemTable();
83     this.strategy = strategy;
84     this.cfVsMaxVersions = cfVsMaxVersions;
85     this.prevFam = new SimpleMutableByteRange();
86     this.prevQual = new SimpleMutableByteRange();
87   }
88 
89   @Override
filterKeyValue(Cell cell)90   public ReturnCode filterKeyValue(Cell cell) {
91     if (isSystemTable) {
92       return ReturnCode.INCLUDE;
93     }
94     if (prevFam.getBytes() == null
95         || (Bytes.compareTo(prevFam.getBytes(), prevFam.getOffset(), prevFam.getLength(),
96             cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength()) != 0)) {
97       prevFam.set(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength());
98       // Similar to VisibilityLabelFilter
99       familyMaxVersions = cfVsMaxVersions.get(prevFam);
100       // Family is changed. Just unset curQualifier.
101       prevQual.unset();
102     }
103     if (prevQual.getBytes() == null
104         || (Bytes.compareTo(prevQual.getBytes(), prevQual.getOffset(),
105             prevQual.getLength(), cell.getQualifierArray(), cell.getQualifierOffset(),
106             cell.getQualifierLength()) != 0)) {
107       prevQual.set(cell.getQualifierArray(), cell.getQualifierOffset(),
108           cell.getQualifierLength());
109       currentVersions = 0;
110     }
111     currentVersions++;
112     if (currentVersions > familyMaxVersions) {
113       return ReturnCode.SKIP;
114     }
115     // XXX: Compare in place, don't clone
116     byte[] family = CellUtil.cloneFamily(cell);
117     byte[] qualifier = CellUtil.cloneQualifier(cell);
118     switch (strategy) {
119       // Filter only by checking the table or CF permissions
120       case CHECK_TABLE_AND_CF_ONLY: {
121         if (authManager.authorize(user, table, family, qualifier, Permission.Action.READ)) {
122           return ReturnCode.INCLUDE;
123         }
124       }
125       break;
126       // Cell permissions can override table or CF permissions
127       case CHECK_CELL_DEFAULT: {
128         if (authManager.authorize(user, table, family, qualifier, Permission.Action.READ) ||
129             authManager.authorize(user, table, cell, Permission.Action.READ)) {
130           return ReturnCode.INCLUDE;
131         }
132       }
133       break;
134       default:
135         throw new RuntimeException("Unhandled strategy " + strategy);
136     }
137 
138     return ReturnCode.SKIP;
139   }
140 
141   // Override here explicitly as the method in super class FilterBase might do a KeyValue recreate.
142   // See HBASE-12068
143   @Override
transformCell(Cell v)144   public Cell transformCell(Cell v) {
145     return v;
146   }
147 
148   @Override
reset()149   public void reset() throws IOException {
150     this.prevFam.unset();
151     this.prevQual.unset();
152     this.familyMaxVersions = 0;
153     this.currentVersions = 0;
154   }
155 
156   /**
157    * @return The filter serialized using pb
158    */
toByteArray()159   public byte [] toByteArray() {
160     // no implementation, server-side use only
161     throw new UnsupportedOperationException(
162       "Serialization not supported.  Intended for server-side use only.");
163   }
164 
165   /**
166    * @param pbBytes A pb serialized {@link AccessControlFilter} instance
167    * @return An instance of {@link AccessControlFilter} made from <code>bytes</code>
168    * @throws org.apache.hadoop.hbase.exceptions.DeserializationException
169    * @see {@link #toByteArray()}
170    */
parseFrom(final byte [] pbBytes)171   public static AccessControlFilter parseFrom(final byte [] pbBytes)
172   throws DeserializationException {
173     // no implementation, server-side use only
174     throw new UnsupportedOperationException(
175       "Serialization not supported.  Intended for server-side use only.");
176   }
177 }
178