blob: f5361adc4cff406839b0b5cbf4087af6e7671949 [file] [log] [blame]
/**
* 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.hdfs.server.namenode;
import org.apache.hadoop.thirdparty.com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.ipc.CallerContext;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
import java.util.Arrays;
@InterfaceAudience.Public
@InterfaceStability.Unstable
public abstract class INodeAttributeProvider {
public static class AuthorizationContext {
private String fsOwner;
private String supergroup;
private UserGroupInformation callerUgi;
private INodeAttributes[] inodeAttrs;
private INode[] inodes;
private byte[][] pathByNameArr;
private int snapshotId;
private String path;
private int ancestorIndex;
private boolean doCheckOwner;
private FsAction ancestorAccess;
private FsAction parentAccess;
private FsAction access;
private FsAction subAccess;
private boolean ignoreEmptyDir;
private String operationName;
private CallerContext callerContext;
public String getFsOwner() {
return fsOwner;
}
public void setFsOwner(String fsOwner) {
this.fsOwner = fsOwner;
}
public String getSupergroup() {
return supergroup;
}
public void setSupergroup(String supergroup) {
this.supergroup = supergroup;
}
public UserGroupInformation getCallerUgi() {
return callerUgi;
}
public void setCallerUgi(UserGroupInformation callerUgi) {
this.callerUgi = callerUgi;
}
public INodeAttributes[] getInodeAttrs() {
return inodeAttrs;
}
public void setInodeAttrs(INodeAttributes[] inodeAttrs) {
this.inodeAttrs = inodeAttrs;
}
public INode[] getInodes() {
return inodes;
}
public void setInodes(INode[] inodes) {
this.inodes = inodes;
}
public byte[][] getPathByNameArr() {
return pathByNameArr;
}
public void setPathByNameArr(byte[][] pathByNameArr) {
this.pathByNameArr = pathByNameArr;
}
public int getSnapshotId() {
return snapshotId;
}
public void setSnapshotId(int snapshotId) {
this.snapshotId = snapshotId;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getAncestorIndex() {
return ancestorIndex;
}
public void setAncestorIndex(int ancestorIndex) {
this.ancestorIndex = ancestorIndex;
}
public boolean isDoCheckOwner() {
return doCheckOwner;
}
public void setDoCheckOwner(boolean doCheckOwner) {
this.doCheckOwner = doCheckOwner;
}
public FsAction getAncestorAccess() {
return ancestorAccess;
}
public void setAncestorAccess(FsAction ancestorAccess) {
this.ancestorAccess = ancestorAccess;
}
public FsAction getParentAccess() {
return parentAccess;
}
public void setParentAccess(FsAction parentAccess) {
this.parentAccess = parentAccess;
}
public FsAction getAccess() {
return access;
}
public void setAccess(FsAction access) {
this.access = access;
}
public FsAction getSubAccess() {
return subAccess;
}
public void setSubAccess(FsAction subAccess) {
this.subAccess = subAccess;
}
public boolean isIgnoreEmptyDir() {
return ignoreEmptyDir;
}
public void setIgnoreEmptyDir(boolean ignoreEmptyDir) {
this.ignoreEmptyDir = ignoreEmptyDir;
}
public String getOperationName() {
return operationName;
}
public void setOperationName(String operationName) {
this.operationName = operationName;
}
public CallerContext getCallerContext() {
return callerContext;
}
public void setCallerContext(CallerContext callerContext) {
this.callerContext = callerContext;
}
public static class Builder {
private String fsOwner;
private String supergroup;
private UserGroupInformation callerUgi;
private INodeAttributes[] inodeAttrs;
private INode[] inodes;
private byte[][] pathByNameArr;
private int snapshotId;
private String path;
private int ancestorIndex;
private boolean doCheckOwner;
private FsAction ancestorAccess;
private FsAction parentAccess;
private FsAction access;
private FsAction subAccess;
private boolean ignoreEmptyDir;
private String operationName;
private CallerContext callerContext;
public AuthorizationContext build() {
return new AuthorizationContext(this);
}
public Builder fsOwner(String val) {
this.fsOwner = val;
return this;
}
public Builder supergroup(String val) {
this.supergroup = val;
return this;
}
public Builder callerUgi(UserGroupInformation val) {
this.callerUgi = val;
return this;
}
public Builder inodeAttrs(INodeAttributes[] val) {
this.inodeAttrs = val;
return this;
}
public Builder inodes(INode[] val) {
this.inodes = val;
return this;
}
public Builder pathByNameArr(byte[][] val) {
this.pathByNameArr = val;
return this;
}
public Builder snapshotId(int val) {
this.snapshotId = val;
return this;
}
public Builder path(String val) {
this.path = val;
return this;
}
public Builder ancestorIndex(int val) {
this.ancestorIndex = val;
return this;
}
public Builder doCheckOwner(boolean val) {
this.doCheckOwner = val;
return this;
}
public Builder ancestorAccess(FsAction val) {
this.ancestorAccess = val;
return this;
}
public Builder parentAccess(FsAction val) {
this.parentAccess = val;
return this;
}
public Builder access(FsAction val) {
this.access = val;
return this;
}
public Builder subAccess(FsAction val) {
this.subAccess = val;
return this;
}
public Builder ignoreEmptyDir(boolean val) {
this.ignoreEmptyDir = val;
return this;
}
public Builder operationName(String val) {
this.operationName = val;
return this;
}
public Builder callerContext(CallerContext val) {
this.callerContext = val;
return this;
}
}
public AuthorizationContext(Builder builder) {
this.setFsOwner(builder.fsOwner);
this.setSupergroup(builder.supergroup);
this.setCallerUgi(builder.callerUgi);
this.setInodeAttrs(builder.inodeAttrs);
this.setInodes(builder.inodes);
this.setPathByNameArr(builder.pathByNameArr);
this.setSnapshotId(builder.snapshotId);
this.setPath(builder.path);
this.setAncestorIndex(builder.ancestorIndex);
this.setDoCheckOwner(builder.doCheckOwner);
this.setAncestorAccess(builder.ancestorAccess);
this.setParentAccess(builder.parentAccess);
this.setAccess(builder.access);
this.setSubAccess(builder.subAccess);
this.setIgnoreEmptyDir(builder.ignoreEmptyDir);
this.setOperationName(builder.operationName);
this.setCallerContext(builder.callerContext);
}
@VisibleForTesting
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj == null || getClass() != obj.getClass()) {
return false;
}
AuthorizationContext other = (AuthorizationContext)obj;
return getFsOwner().equals(other.getFsOwner()) &&
getSupergroup().equals(other.getSupergroup()) &&
getCallerUgi().equals(other.getCallerUgi()) &&
Arrays.deepEquals(getInodeAttrs(), other.getInodeAttrs()) &&
Arrays.deepEquals(getInodes(), other.getInodes()) &&
Arrays.deepEquals(getPathByNameArr(), other.getPathByNameArr()) &&
getSnapshotId() == other.getSnapshotId() &&
getPath().equals(other.getPath()) &&
getAncestorIndex() == other.getAncestorIndex() &&
isDoCheckOwner() == other.isDoCheckOwner() &&
getAncestorAccess() == other.getAncestorAccess() &&
getParentAccess() == other.getParentAccess() &&
getAccess() == other.getAccess() &&
getSubAccess() == other.getSubAccess() &&
isIgnoreEmptyDir() == other.isIgnoreEmptyDir();
}
@Override
public int hashCode() {
assert false : "hashCode not designed";
return 42; // any arbitrary constant will do
}
}
/**
* The AccessControlEnforcer allows implementations to override the
* default File System permission checking logic enforced on a file system
* object
*/
public interface AccessControlEnforcer {
/**
* Checks permission on a file system object. Has to throw an Exception
* if the filesystem object is not accessible by the calling Ugi.
* @param fsOwner Filesystem owner (The Namenode user)
* @param supergroup super user group
* @param callerUgi UserGroupInformation of the caller
* @param inodeAttrs Array of INode attributes for each path element in the
* the path
* @param inodes Array of INodes for each path element in the path
* @param pathByNameArr Array of byte arrays of the LocalName
* @param snapshotId the snapshotId of the requested path
* @param path Path String
* @param ancestorIndex Index of ancestor
* @param doCheckOwner perform ownership check
* @param ancestorAccess The access required by the ancestor of the path.
* @param parentAccess The access required by the parent of the path.
* @param access The access required by the path.
* @param subAccess If path is a directory, It is the access required of
* the path and all the sub-directories. If path is not a
* directory, there should ideally be no effect.
* @param ignoreEmptyDir Ignore permission checking for empty directory?
* @deprecated use{@link #checkPermissionWithContext(AuthorizationContext)}}
* instead
* @throws AccessControlException
*/
public abstract void checkPermission(String fsOwner, String supergroup,
UserGroupInformation callerUgi, INodeAttributes[] inodeAttrs,
INode[] inodes, byte[][] pathByNameArr, int snapshotId, String path,
int ancestorIndex, boolean doCheckOwner, FsAction ancestorAccess,
FsAction parentAccess, FsAction access, FsAction subAccess,
boolean ignoreEmptyDir)
throws AccessControlException;
/**
* Checks permission on a file system object. Has to throw an Exception
* if the filesystem object is not accessible by the calling Ugi.
* @param authzContext an {@link AuthorizationContext} object encapsulating
* the various parameters required to authorize an
* operation.
* @throws AccessControlException
*/
default void checkPermissionWithContext(AuthorizationContext authzContext)
throws AccessControlException {
throw new AccessControlException("The authorization provider does not "
+ "implement the checkPermissionWithContext(AuthorizationContext) "
+ "API.");
}
/**
* Checks if the user is a superuser or belongs to superuser group.
* It throws an AccessControlException if user is not a superuser.
*
* @param authzContext an {@link AuthorizationContext} object encapsulating
* the various parameters required to authorize an
* operation.
* @throws AccessControlException - if user is not a super user or part
* of the super user group.
*/
default void checkSuperUserPermissionWithContext(
AuthorizationContext authzContext)
throws AccessControlException {
UserGroupInformation callerUgi = authzContext.getCallerUgi();
boolean isSuperUser =
callerUgi.getShortUserName().equals(authzContext.getFsOwner()) ||
callerUgi.getGroupsSet().contains(authzContext.getSupergroup());
if (!isSuperUser) {
throw new AccessControlException("Access denied for user " +
callerUgi.getShortUserName() + ". Superuser privilege is " +
"required for operation " + authzContext.getOperationName());
}
}
/**
* This method must be called when denying access to users to
* notify the external enforcers.
* This will help the external enforcers to audit the requests
* by users that were denied access.
* @param authzContext an {@link AuthorizationContext} object encapsulating
* the various parameters required to authorize an
* operation.
* @throws AccessControlException
*/
default void denyUserAccess(AuthorizationContext authzContext,
String errorMessage)
throws AccessControlException {
throw new AccessControlException(errorMessage);
}
}
/**
* Initialize the provider. This method is called at NameNode startup
* time.
*/
public abstract void start();
/**
* Shutdown the provider. This method is called at NameNode shutdown time.
*/
public abstract void stop();
@Deprecated
String[] getPathElements(String path) {
path = path.trim();
if (path.charAt(0) != Path.SEPARATOR_CHAR) {
throw new IllegalArgumentException("It must be an absolute path: " +
path);
}
int numOfElements = StringUtils.countMatches(path, Path.SEPARATOR);
if (path.length() > 1 && path.endsWith(Path.SEPARATOR)) {
numOfElements--;
}
String[] pathElements = new String[numOfElements];
int elementIdx = 0;
int idx = 0;
int found = path.indexOf(Path.SEPARATOR_CHAR, idx);
while (found > -1) {
if (found > idx) {
pathElements[elementIdx++] = path.substring(idx, found);
}
idx = found + 1;
found = path.indexOf(Path.SEPARATOR_CHAR, idx);
}
if (idx < path.length()) {
pathElements[elementIdx] = path.substring(idx);
}
return pathElements;
}
@Deprecated
public INodeAttributes getAttributes(String fullPath, INodeAttributes inode) {
return getAttributes(getPathElements(fullPath), inode);
}
public abstract INodeAttributes getAttributes(String[] pathElements,
INodeAttributes inode);
public INodeAttributes getAttributes(byte[][] components,
INodeAttributes inode) {
String[] elements = new String[components.length];
for (int i = 0; i < elements.length; i++) {
elements[i] = DFSUtil.bytes2String(components[i]);
}
return getAttributes(elements, inode);
}
/**
* Can be over-ridden by implementations to provide a custom Access Control
* Enforcer that can provide an alternate implementation of the
* default permission checking logic.
* @param defaultEnforcer The Default AccessControlEnforcer
* @return The AccessControlEnforcer to use
*/
public AccessControlEnforcer getExternalAccessControlEnforcer(
AccessControlEnforcer defaultEnforcer) {
return defaultEnforcer;
}
}