001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.security.access; 019 020import java.io.IOException; 021import java.util.Collection; 022import java.util.Optional; 023import org.apache.commons.io.FilenameUtils; 024import org.apache.hadoop.fs.Path; 025import org.apache.hadoop.hbase.HBaseInterfaceAudience; 026import org.apache.hadoop.hbase.TableName; 027import org.apache.hadoop.hbase.client.CoprocessorDescriptor; 028import org.apache.hadoop.hbase.client.RegionInfo; 029import org.apache.hadoop.hbase.client.TableDescriptor; 030import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; 031import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 032import org.apache.hadoop.hbase.coprocessor.MasterObserver; 033import org.apache.hadoop.hbase.coprocessor.ObserverContext; 034import org.apache.yetus.audience.InterfaceAudience; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038/** 039 * Master observer for restricting coprocessor assignments. 040 */ 041@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) 042public class CoprocessorWhitelistMasterObserver implements MasterCoprocessor, MasterObserver { 043 044 public static final String CP_COPROCESSOR_WHITELIST_PATHS_KEY = 045 "hbase.coprocessor.region.whitelist.paths"; 046 047 private static final Logger LOG = 048 LoggerFactory.getLogger(CoprocessorWhitelistMasterObserver.class); 049 050 @Override 051 public Optional<MasterObserver> getMasterObserver() { 052 return Optional.of(this); 053 } 054 055 @Override 056 public TableDescriptor preModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx, 057 TableName tableName, TableDescriptor currentDesc, TableDescriptor newDesc) throws IOException { 058 verifyCoprocessors(ctx, newDesc); 059 return newDesc; 060 } 061 062 @Override 063 public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableDescriptor htd, 064 RegionInfo[] regions) throws IOException { 065 verifyCoprocessors(ctx, htd); 066 } 067 068 /** 069 * Validates a single whitelist path against the coprocessor path 070 * @param coprocPath the path to the coprocessor including scheme 071 * @param wlPath can be: 1) a "*" to wildcard all coprocessor paths 2) a specific filesystem 072 * (e.g. hdfs://my-cluster/) 3) a wildcard path to be evaluated by 073 * {@link FilenameUtils#wildcardMatch(String, String)} path can specify scheme 074 * or not (e.g. "file:///usr/hbase/coprocessors" or for all filesystems 075 * "/usr/hbase/coprocessors") 076 * @return if the path was found under the wlPath 077 */ 078 private static boolean validatePath(Path coprocPath, Path wlPath) { 079 // verify if all are allowed 080 if (wlPath.toString().equals("*")) { 081 return (true); 082 } 083 084 // verify we are on the same filesystem if wlPath has a scheme 085 if (!wlPath.isAbsoluteAndSchemeAuthorityNull()) { 086 String wlPathScheme = wlPath.toUri().getScheme(); 087 String coprocPathScheme = coprocPath.toUri().getScheme(); 088 String wlPathHost = wlPath.toUri().getHost(); 089 String coprocPathHost = coprocPath.toUri().getHost(); 090 if (wlPathScheme != null) { 091 wlPathScheme = wlPathScheme.toString().toLowerCase(); 092 } else { 093 wlPathScheme = ""; 094 } 095 if (wlPathHost != null) { 096 wlPathHost = wlPathHost.toString().toLowerCase(); 097 } else { 098 wlPathHost = ""; 099 } 100 if (coprocPathScheme != null) { 101 coprocPathScheme = coprocPathScheme.toString().toLowerCase(); 102 } else { 103 coprocPathScheme = ""; 104 } 105 if (coprocPathHost != null) { 106 coprocPathHost = coprocPathHost.toString().toLowerCase(); 107 } else { 108 coprocPathHost = ""; 109 } 110 if (!wlPathScheme.equals(coprocPathScheme) || !wlPathHost.equals(coprocPathHost)) { 111 return (false); 112 } 113 } 114 115 // allow any on this file-system (file systems were verified to be the same above) 116 if (wlPath.isRoot()) { 117 return (true); 118 } 119 120 // allow "loose" matches stripping scheme 121 if ( 122 FilenameUtils.wildcardMatch(Path.getPathWithoutSchemeAndAuthority(coprocPath).toString(), 123 Path.getPathWithoutSchemeAndAuthority(wlPath).toString()) 124 ) { 125 return (true); 126 } 127 return (false); 128 } 129 130 /** 131 * Perform the validation checks for a coprocessor to determine if the path is white listed or 132 * not. 133 * @throws IOException if path is not included in whitelist or a failure occurs in processing 134 * @param ctx as passed in from the coprocessor 135 * @param htd as passed in from the coprocessor 136 */ 137 private static void verifyCoprocessors(ObserverContext<MasterCoprocessorEnvironment> ctx, 138 TableDescriptor htd) throws IOException { 139 Collection<String> paths = ctx.getEnvironment().getConfiguration() 140 .getStringCollection(CP_COPROCESSOR_WHITELIST_PATHS_KEY); 141 for (CoprocessorDescriptor cp : htd.getCoprocessorDescriptors()) { 142 if (cp.getJarPath().isPresent()) { 143 if (paths.stream().noneMatch(p -> { 144 Path wlPath = new Path(p); 145 if (validatePath(new Path(cp.getJarPath().get()), wlPath)) { 146 LOG.debug(String.format("Coprocessor %s found in directory %s", cp.getClassName(), p)); 147 return true; 148 } 149 return false; 150 })) { 151 throw new IOException(String.format("Loading %s DENIED in %s", cp.getClassName(), 152 CP_COPROCESSOR_WHITELIST_PATHS_KEY)); 153 } 154 } 155 } 156 } 157}