1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.hadoop.hbase.security.access;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.apache.hadoop.classification.InterfaceAudience;
25 import org.apache.hadoop.conf.Configuration;
26 import org.apache.hadoop.fs.FileStatus;
27 import org.apache.hadoop.fs.FileSystem;
28 import org.apache.hadoop.fs.Path;
29 import org.apache.hadoop.fs.permission.FsPermission;
30 import org.apache.hadoop.hbase.CoprocessorEnvironment;
31 import org.apache.hadoop.hbase.DoNotRetryIOException;
32 import org.apache.hadoop.hbase.coprocessor.BaseEndpointCoprocessor;
33 import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
34 import org.apache.hadoop.hbase.ipc.RequestContext;
35 import org.apache.hadoop.hbase.regionserver.HRegion;
36 import org.apache.hadoop.hbase.security.User;
37 import org.apache.hadoop.hbase.util.Bytes;
38 import org.apache.hadoop.hbase.util.Methods;
39 import org.apache.hadoop.hbase.util.Pair;
40 import org.apache.hadoop.security.UserGroupInformation;
41 import org.apache.hadoop.security.token.Token;
42
43 import java.io.IOException;
44 import java.math.BigInteger;
45 import java.security.PrivilegedAction;
46 import java.security.SecureRandom;
47 import java.util.List;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 @InterfaceAudience.Private
76 public class SecureBulkLoadEndpoint extends BaseEndpointCoprocessor
77 implements SecureBulkLoadProtocol {
78
79 public static final long VERSION = 0L;
80
81
82 private static final int RANDOM_WIDTH = 320;
83
84
85
86 private static final int RANDOM_RADIX = 32;
87
88 private static Log LOG = LogFactory.getLog(SecureBulkLoadEndpoint.class);
89
90 private final static FsPermission PERM_ALL_ACCESS = FsPermission.valueOf("-rwxrwxrwx");
91 private final static FsPermission PERM_HIDDEN = FsPermission.valueOf("-rwx--x--x");
92 private final static String BULKLOAD_STAGING_DIR = "hbase.bulkload.staging.dir";
93
94 private SecureRandom random;
95 private FileSystem fs;
96 private Configuration conf;
97
98
99
100 private Path baseStagingDir;
101
102 private RegionCoprocessorEnvironment env;
103
104
105 @Override
106 public void start(CoprocessorEnvironment env) {
107 super.start(env);
108
109 this.env = (RegionCoprocessorEnvironment)env;
110 random = new SecureRandom();
111 conf = env.getConfiguration();
112 baseStagingDir = getBaseStagingDir(conf);
113
114 try {
115 fs = FileSystem.get(conf);
116 fs.mkdirs(baseStagingDir, PERM_HIDDEN);
117 fs.setPermission(baseStagingDir, PERM_HIDDEN);
118
119 fs.mkdirs(new Path(baseStagingDir,"DONOTERASE"), PERM_HIDDEN);
120 FileStatus status = fs.getFileStatus(baseStagingDir);
121 if(status == null) {
122 throw new IllegalStateException("Failed to create staging directory");
123 }
124 if(!status.getPermission().equals(PERM_HIDDEN)) {
125 throw new IllegalStateException("Directory already exists but permissions aren't set to '-rwx--x--x' ");
126 }
127 } catch (IOException e) {
128 throw new IllegalStateException("Failed to get FileSystem instance",e);
129 }
130 }
131
132 @Override
133 public String prepareBulkLoad(byte[] tableName) throws IOException {
134 getAccessController().prePrepareBulkLoad(env);
135 return createStagingDir(baseStagingDir, getActiveUser(), tableName).toString();
136 }
137
138 @Override
139 public void cleanupBulkLoad(String bulkToken) throws IOException {
140 getAccessController().preCleanupBulkLoad(env);
141 fs.delete(createStagingDir(baseStagingDir,
142 getActiveUser(),
143 env.getRegion().getTableDesc().getName(),
144 new Path(bulkToken).getName()),
145 true);
146 }
147
148 @Override
149 public boolean bulkLoadHFiles(final List<Pair<byte[], String>> familyPaths,
150 final Token<?> userToken, final String bulkToken) throws IOException {
151 User user = getActiveUser();
152 final UserGroupInformation ugi = user.getUGI();
153 if(userToken != null) {
154 ugi.addToken(userToken);
155 } else if(User.isSecurityEnabled()) {
156
157
158 throw new DoNotRetryIOException("User token cannot be null");
159 }
160
161 HRegion region = env.getRegion();
162 boolean bypass = false;
163 if (region.getCoprocessorHost() != null) {
164 bypass = region.getCoprocessorHost().preBulkLoadHFile(familyPaths);
165 }
166 boolean loaded = false;
167 if (!bypass) {
168 loaded = ugi.doAs(new PrivilegedAction<Boolean>() {
169 @Override
170 public Boolean run() {
171 FileSystem fs = null;
172 try {
173 Configuration conf = env.getConfiguration();
174 fs = FileSystem.get(conf);
175 for(Pair<byte[], String> el: familyPaths) {
176 Path p = new Path(el.getSecond());
177 LOG.debug("Setting permission for: " + p);
178 fs.setPermission(p, PERM_ALL_ACCESS);
179 Path stageFamily = new Path(bulkToken, Bytes.toString(el.getFirst()));
180 if(!fs.exists(stageFamily)) {
181 fs.mkdirs(stageFamily);
182 fs.setPermission(stageFamily, PERM_ALL_ACCESS);
183 }
184 }
185
186
187 return env.getRegion().bulkLoadHFiles(familyPaths,
188 new SecureBulkLoadListener(fs, bulkToken));
189 } catch (Exception e) {
190 LOG.error("Failed to complete bulk load", e);
191 }
192 return false;
193 }
194 });
195 }
196 if (region.getCoprocessorHost() != null) {
197 loaded = region.getCoprocessorHost().postBulkLoadHFile(familyPaths, loaded);
198 }
199 return loaded;
200 }
201
202 @Override
203 public long getProtocolVersion(String protocol, long clientVersion)
204 throws IOException {
205 if (SecureBulkLoadProtocol.class.getName().equals(protocol)) {
206 return SecureBulkLoadEndpoint.VERSION;
207 }
208 LOG.warn("Unknown protocol requested: " + protocol);
209 return -1;
210 }
211
212 private AccessController getAccessController() {
213 return (AccessController) this.env.getRegion()
214 .getCoprocessorHost().findCoprocessor(AccessController.class.getName());
215 }
216
217 private Path createStagingDir(Path baseDir, User user, byte[] tableName) throws IOException {
218 String randomDir = user.getShortName()+"__"+Bytes.toString(tableName)+"__"+
219 (new BigInteger(RANDOM_WIDTH, random).toString(RANDOM_RADIX));
220 return createStagingDir(baseDir, user, tableName, randomDir);
221 }
222
223 private Path createStagingDir(Path baseDir,
224 User user,
225 byte[] tableName,
226 String randomDir) throws IOException {
227 Path p = new Path(baseDir, randomDir);
228 fs.mkdirs(p, PERM_ALL_ACCESS);
229 fs.setPermission(p, PERM_ALL_ACCESS);
230 return p;
231 }
232
233 private User getActiveUser() throws IOException {
234 User user = RequestContext.getRequestUser();
235 if (!RequestContext.isInRequestContext()) {
236 throw new DoNotRetryIOException("Failed to get requesting user");
237 }
238
239
240 if("simple".equalsIgnoreCase(conf.get(User.HBASE_SECURITY_CONF_KEY))) {
241 return User.createUserForTesting(conf, user.getShortName(), new String[]{});
242 }
243
244 return user;
245 }
246
247
248
249
250
251 public static Path getStagingPath(Configuration conf, String bulkToken, byte[] family) {
252 Path stageP = new Path(getBaseStagingDir(conf), bulkToken);
253 return new Path(stageP, Bytes.toString(family));
254 }
255
256 private static Path getBaseStagingDir(Configuration conf) {
257 return new Path(conf.get(BULKLOAD_STAGING_DIR, "/tmp/hbase-staging"));
258 }
259
260 private static class SecureBulkLoadListener implements HRegion.BulkLoadListener {
261 private FileSystem fs;
262 private String stagingDir;
263
264 public SecureBulkLoadListener(FileSystem fs, String stagingDir) {
265 this.fs = fs;
266 this.stagingDir = stagingDir;
267 }
268
269 @Override
270 public String prepareBulkLoad(final byte[] family, final String srcPath) throws IOException {
271 Path p = new Path(srcPath);
272 Path stageP = new Path(stagingDir, new Path(Bytes.toString(family), p.getName()));
273
274 if(!isFile(p)) {
275 throw new IOException("Path does not reference a file: " + p);
276 }
277
278 LOG.debug("Moving " + p + " to " + stageP);
279 if(!fs.rename(p, stageP)) {
280 throw new IOException("Failed to move HFile: " + p + " to " + stageP);
281 }
282 return stageP.toString();
283 }
284
285 @Override
286 public void doneBulkLoad(byte[] family, String srcPath) throws IOException {
287 LOG.debug("Bulk Load done for: " + srcPath);
288 }
289
290 @Override
291 public void failedBulkLoad(final byte[] family, final String srcPath) throws IOException {
292 Path p = new Path(srcPath);
293 Path stageP = new Path(stagingDir,
294 new Path(Bytes.toString(family), p.getName()));
295 LOG.debug("Moving " + stageP + " back to " + p);
296 if(!fs.rename(stageP, p))
297 throw new IOException("Failed to move HFile: " + stageP + " to " + p);
298 }
299
300
301
302
303
304
305
306
307 private boolean isFile(Path p) throws IOException {
308 FileStatus status = fs.getFileStatus(p);
309 boolean isFile = !status.isDir();
310 try {
311 isFile = isFile && !(Boolean)Methods.call(FileStatus.class, status, "isSymlink", null, null);
312 } catch (Exception e) {
313 }
314 return isFile;
315 }
316 }
317 }