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}