+ * 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.knox.gateway.util;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authorize.AccessControlList;
+import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.authorize.DefaultImpersonationProvider;
+import org.apache.hadoop.util.MachineList;
+import org.apache.knox.gateway.i18n.GatewaySpiMessages;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+
+import java.net.InetAddress;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.apache.knox.gateway.util.AuthFilterUtils.PROXYGROUP_PREFIX;
+import static org.apache.knox.gateway.util.AuthFilterUtils.PROXYUSER_PREFIX;
+
+/**
+ * An extension of Hadoop's DefaultImpersonationProvider that adds support for group-based impersonation.
+ * This provider allows users who belong to specific groups to impersonate other users.
+ */
+public class GroupBasedImpersonationProvider extends DefaultImpersonationProvider {
+ private static final GatewaySpiMessages LOG = MessagesFactory.get(GatewaySpiMessages.class);
+ private static final String CONF_HOSTS = ".hosts";
+ private static final String CONF_USERS = ".users";
+ private static final String CONF_GROUPS = ".groups";
+ private static final String PREFIX_REGEX_EXP = "\\.";
+ private static final String USERS_GROUPS_REGEX_EXP = "[\\S]*(" +
+ Pattern.quote(CONF_USERS) + "|" + Pattern.quote(CONF_GROUPS) + ")";
+ private static final String HOSTS_REGEX_EXP = "[\\S]*" + Pattern.quote(CONF_HOSTS);
+ private final Map
String instead of char[].
- *
- * @param conf the configuration
- * @param name the property name
- * @return the password string or null
- */
- private static String getPasswordString(Configuration conf, String name)
- throws IOException {
- char[] passchars = conf.getPassword(name);
- if (passchars == null) {
- return null;
- }
- return new String(passchars);
- }
-
- /**
- * Load SSL properties from the SSL configuration.
- */
- @SuppressForbidden
- private void loadSSLConfiguration() throws IOException {
- if (sslConf == null) {
- return;
- }
- needsClientAuth = sslConf.getBoolean(
- SSLFactory.SSL_SERVER_NEED_CLIENT_AUTH,
- SSLFactory.SSL_SERVER_NEED_CLIENT_AUTH_DEFAULT);
- keyStore = sslConf.getTrimmed(SSLFactory.SSL_SERVER_KEYSTORE_LOCATION);
- if (keyStore == null || keyStore.isEmpty()) {
- throw new IOException(String.format("Property %s not specified",
- SSLFactory.SSL_SERVER_KEYSTORE_LOCATION));
- }
- keyStorePassword = getPasswordString(sslConf,
- SSLFactory.SSL_SERVER_KEYSTORE_PASSWORD);
- if (keyStorePassword == null) {
- throw new IOException(String.format("Property %s not specified",
- SSLFactory.SSL_SERVER_KEYSTORE_PASSWORD));
- }
- keyStoreType = sslConf.get(SSLFactory.SSL_SERVER_KEYSTORE_TYPE,
- SSLFactory.SSL_SERVER_KEYSTORE_TYPE_DEFAULT);
- keyPassword = getPasswordString(sslConf,
- SSLFactory.SSL_SERVER_KEYSTORE_KEYPASSWORD);
- trustStore = sslConf.get(SSLFactory.SSL_SERVER_TRUSTSTORE_LOCATION);
- trustStorePassword = getPasswordString(sslConf,
- SSLFactory.SSL_SERVER_TRUSTSTORE_PASSWORD);
- trustStoreType = sslConf.get(SSLFactory.SSL_SERVER_TRUSTSTORE_TYPE,
- SSLFactory.SSL_SERVER_TRUSTSTORE_TYPE_DEFAULT);
- excludeCiphers = sslConf.get(SSLFactory.SSL_SERVER_EXCLUDE_CIPHER_LIST);
- }
-
- public HttpServer2 build() throws IOException {
- Preconditions.checkNotNull(name, "name is not set");
- Preconditions.checkState(!endpoints.isEmpty(), "No endpoints specified");
-
- if (hostName == null) {
- hostName = endpoints.get(0).getHost();
- }
-
- if (this.conf == null) {
- conf = new Configuration();
- }
-
- HttpServer2 server = new HttpServer2(this); // NOPMD
-
- if (this.securityEnabled) {
- server.initSpnego(conf, hostName, usernameConfKey, keytabConfKey);
- }
-
- for (URI ep : endpoints) {
- if (HTTPS_SCHEME.equals(ep.getScheme())) {
- loadSSLConfiguration();
- break;
- }
- }
-
- int requestHeaderSize = conf.getInt(
- HTTP_MAX_REQUEST_HEADER_SIZE_KEY,
- HTTP_MAX_REQUEST_HEADER_SIZE_DEFAULT);
- int responseHeaderSize = conf.getInt(
- HTTP_MAX_RESPONSE_HEADER_SIZE_KEY,
- HTTP_MAX_RESPONSE_HEADER_SIZE_DEFAULT);
- int idleTimeout = conf.getInt(HTTP_IDLE_TIMEOUT_MS_KEY,
- HTTP_IDLE_TIMEOUT_MS_DEFAULT);
-
- HttpConfiguration httpConfig = new HttpConfiguration();
- httpConfig.setRequestHeaderSize(requestHeaderSize);
- httpConfig.setResponseHeaderSize(responseHeaderSize);
- httpConfig.setSendServerVersion(false);
-
- int backlogSize = conf.getInt(HTTP_SOCKET_BACKLOG_SIZE_KEY,
- HTTP_SOCKET_BACKLOG_SIZE_DEFAULT);
-
- for (URI ep : endpoints) {
- final ServerConnector connector;
- String scheme = ep.getScheme();
- if (HTTP_SCHEME.equals(scheme)) {
- connector = createHttpChannelConnector(server.webServer,
- httpConfig);
- } else if (HTTPS_SCHEME.equals(scheme)) {
- connector = createHttpsChannelConnector(server.webServer,
- httpConfig);
- } else {
- throw new HadoopIllegalArgumentException(
- "unknown scheme for endpoint:" + ep);
- }
- connector.setHost(ep.getHost());
- connector.setPort(ep.getPort() == -1 ? 0 : ep.getPort());
- connector.setAcceptQueueSize(backlogSize);
- connector.setIdleTimeout(idleTimeout);
- server.addListener(connector);
- }
- server.loadListeners();
- return server;
- }
-
- private ServerConnector createHttpChannelConnector(
- Server server, HttpConfiguration httpConfig) {
- ServerConnector conn = new ServerConnector(server,
- conf.getInt(HTTP_ACCEPTOR_COUNT_KEY, HTTP_ACCEPTOR_COUNT_DEFAULT),
- conf.getInt(HTTP_SELECTOR_COUNT_KEY, HTTP_SELECTOR_COUNT_DEFAULT));
- ConnectionFactory connFactory = new HttpConnectionFactory(httpConfig);
- conn.addConnectionFactory(connFactory);
- if(Shell.WINDOWS) {
- // result of setting the SO_REUSEADDR flag is different on Windows
- // http://msdn.microsoft.com/en-us/library/ms740621(v=vs.85).aspx
- // without this 2 NN's can start on the same machine and listen on
- // the same port with indeterminate routing of incoming requests to them
- conn.setReuseAddress(false);
- }
- return conn;
- }
-
- private ServerConnector createHttpsChannelConnector(
- Server server, HttpConfiguration httpConfig) {
- httpConfig.setSecureScheme(HTTPS_SCHEME);
- httpConfig.addCustomizer(new SecureRequestCustomizer());
- ServerConnector conn = createHttpChannelConnector(server, httpConfig);
-
- SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
- sslContextFactory.setNeedClientAuth(needsClientAuth);
- sslContextFactory.setKeyManagerPassword(keyPassword);
- if (keyStore != null) {
- sslContextFactory.setKeyStorePath(keyStore);
- sslContextFactory.setKeyStoreType(keyStoreType);
- sslContextFactory.setKeyStorePassword(keyStorePassword);
- }
- if (trustStore != null) {
- sslContextFactory.setTrustStorePath(trustStore);
- sslContextFactory.setTrustStoreType(trustStoreType);
- sslContextFactory.setTrustStorePassword(trustStorePassword);
- }
- if(null != excludeCiphers && !excludeCiphers.isEmpty()) {
- sslContextFactory.setExcludeCipherSuites(
- StringUtils.getTrimmedStrings(excludeCiphers));
- LOG.info("Excluded Cipher List:" + excludeCiphers);
- }
-
- conn.addFirstConnectionFactory(new SslConnectionFactory(sslContextFactory,
- HttpVersion.HTTP_1_1.asString()));
-
- return conn;
- }
- }
-
- private HttpServer2(final Builder b) throws IOException {
- final String appDir = getWebAppsPath(b.name);
- this.webServer = new Server();
- this.adminsAcl = b.adminsAcl;
- this.handlers = new HandlerCollection();
- this.webAppContext = createWebAppContext(b, adminsAcl, appDir);
- this.xFrameOptionIsEnabled = b.xFrameEnabled;
- this.xFrameOption = b.xFrameOption;
-
- try {
- this.secretProvider =
- constructSecretProvider(b, webAppContext.getServletContext());
- this.webAppContext.getServletContext().setAttribute
- (AuthenticationFilter.SIGNER_SECRET_PROVIDER_ATTRIBUTE,
- secretProvider);
- } catch(IOException e) {
- throw e;
- } catch (Exception e) {
- throw new IOException(e);
- }
-
- this.findPort = b.findPort;
- this.portRanges = b.portRanges;
- initializeWebServer(b.name, b.hostName, b.conf, b.pathSpecs);
- }
-
- private void initializeWebServer(String name, String hostName,
- Configuration conf, String[] pathSpecs)
- throws IOException {
-
- Preconditions.checkNotNull(webAppContext);
-
- int maxThreads = conf.getInt(HTTP_MAX_THREADS_KEY, -1);
- // If HTTP_MAX_THREADS is not configured, QueueThreadPool() will use the
- // default value (currently 250).
-
- QueuedThreadPool threadPool = (QueuedThreadPool) webServer.getThreadPool();
- threadPool.setDaemon(true);
- if (maxThreads != -1) {
- // Minimum number of threads must be > 3.
- // DatanodeHttpServer sets the HTTP_MAX_THREADS_KEY to 3
- threadPool.setMaxThreads(Math.max(maxThreads, 4));
- }
-
- SessionHandler sessionHandler = webAppContext.getSessionHandler();
- sessionHandler.setHttpOnly(true);
- sessionHandler.getSessionCookieConfig().setSecure(true);
-
- ContextHandlerCollection contexts = new ContextHandlerCollection();
- RequestLog requestLog = HttpRequestLog.getRequestLog(name);
-
- handlers.addHandler(contexts);
- if (requestLog != null) {
- RequestLogHandler requestLogHandler = new RequestLogHandler();
- requestLogHandler.setRequestLog(requestLog);
- handlers.addHandler(requestLogHandler);
- }
- handlers.addHandler(webAppContext);
- final String appDir = getWebAppsPath(name);
- addDefaultApps(contexts, appDir, conf);
- webServer.setHandler(handlers);
-
- Maphadoop.security.instrumentation.requires.admin is set to FALSE
- * (default value) it always returns TRUE.
- *
- * If hadoop.security.instrumentation.requires.admin is set to TRUE
- * it will check that if the current user is in the admin ACLS. If the user is
- * in the admin ACLs it returns TRUE, otherwise it returns FALSE.
- *
- * @param servletContext the servlet context.
- * @param request the servlet request.
- * @param response the servlet response.
- * @return TRUE/FALSE based on the logic described above.
- * @throws IOException exception on error
- */
- public static boolean isInstrumentationAccessAllowed(
- ServletContext servletContext, HttpServletRequest request,
- HttpServletResponse response) throws IOException {
- Configuration conf =
- (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
-
- boolean access = true;
- boolean adminAccess = conf.getBoolean(
- CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN,
- false);
- if (adminAccess) {
- access = hasAdministratorAccess(servletContext, request, response);
- }
- return access;
- }
-
- /**
- * Does the user sending the HttpServletRequest has the administrator ACLs? If
- * it isn't the case, response will be modified to send an error to the user.
- *
- * @param servletContext the servlet context.
- * @param request the servlet request.
- * @param response used to send the error response if user does not have admin access.
- * @return true if admin-authorized, false otherwise
- * @throws IOException exception on error
- */
- public static boolean hasAdministratorAccess(
- ServletContext servletContext, HttpServletRequest request,
- HttpServletResponse response) throws IOException {
- Configuration conf =
- (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
- // If there is no authorization, anybody has administrator access.
- if (!conf.getBoolean(
- CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
- return true;
- }
-
- String remoteUser = request.getRemoteUser();
- if (remoteUser == null) {
- response.sendError(HttpServletResponse.SC_FORBIDDEN,
- "Unauthenticated users are not " +
- "authorized to access this page.");
- return false;
- }
-
- if (servletContext.getAttribute(ADMINS_ACL) != null &&
- !userHasAdministratorAccess(servletContext, remoteUser)) {
- response.sendError(HttpServletResponse.SC_FORBIDDEN,
- "Unauthenticated users are not " +
- "authorized to access this page.");
- LOG.warn("User " + remoteUser + " is unauthorized to access the page "
- + request.getRequestURI() + ".");
- return false;
- }
-
- return true;
- }
-
- /**
- * Get the admin ACLs from the given ServletContext and check if the given
- * user is in the ACL.
- *
- * @param servletContext the context containing the admin ACL.
- * @param remoteUser the remote user to check for.
- * @return true if the user is present in the ACL, false if no ACL is set or
- * the user is not present
- */
- public static boolean userHasAdministratorAccess(ServletContext servletContext,
- String remoteUser) {
- AccessControlList adminsAcl = (AccessControlList) servletContext
- .getAttribute(ADMINS_ACL);
- UserGroupInformation remoteUserUGI =
- UserGroupInformation.createRemoteUser(remoteUser);
- return adminsAcl != null && adminsAcl.isUserAllowed(remoteUserUGI);
- }
-
- /**
- * A very simple servlet to serve up a text representation of the current
- * stack traces. It both returns the stacks to the caller and logs them.
- * Currently the stack traces are done sequentially rather than exactly the
- * same data.
- */
- public static class StackServlet extends HttpServlet {
- private static final long serialVersionUID = -6284183679759467039L;
-
- @Override
- public void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- if (!HttpServer2.isInstrumentationAccessAllowed(getServletContext(),
- request, response)) {
- return;
- }
- response.setContentType("text/plain; charset=UTF-8");
- try (PrintStream out = new PrintStream(
- response.getOutputStream(), false, "UTF-8")) {
- ReflectionUtils.printThreadInfo(out, "");
- }
- ReflectionUtils.logThreadInfo(LOG, "jsp requested", 1);
- }
- }
-
- /**
- * A Servlet input filter that quotes all HTML active characters in the
- * parameter names and values. The goal is to quote the characters to make
- * all of the servlets resistant to cross-site scripting attacks. It also
- * sets X-FRAME-OPTIONS in the header to mitigate clickjacking attacks.
- */
- public static class QuotingInputFilter implements Filter {
-
- private FilterConfig config;
- private Map