blob: eb92f2c052bf5956a29065101df08c1511271036 [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.storm.daemon.ui.filters;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.security.Principal;
import java.util.Map;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import org.apache.commons.codec.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.storm.DaemonConfig;
import org.apache.storm.daemon.StormCommon;
import org.apache.storm.daemon.common.JsonResponseBuilder;
import org.apache.storm.daemon.ui.UIHelpers;
import org.apache.storm.daemon.ui.resources.AuthNimbusOp;
import org.apache.storm.daemon.ui.resources.StormApiResource;
import org.apache.storm.generated.AuthorizationException;
import org.apache.storm.security.auth.IAuthorizer;
import org.apache.storm.security.auth.ReqContext;
import org.apache.storm.thrift.TException;
import org.apache.storm.utils.NimbusClient;
import org.apache.storm.utils.Utils;
import org.json.simple.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Provider
public class AuthorizedUserFilter implements ContainerRequestFilter {
public static final Logger LOG = LoggerFactory.getLogger(AuthorizedUserFilter.class);
public static Map<String, Object> conf = Utils.readStormConfig();
public static IAuthorizer uiImpersonationHandler;
public static IAuthorizer uiAclHandler;
@Context private ResourceInfo resourceInfo;
static {
try {
uiImpersonationHandler = StormCommon.mkAuthorizationHandler(
(String) conf.get(DaemonConfig.NIMBUS_IMPERSONATION_AUTHORIZER), conf);
uiAclHandler = StormCommon.mkAuthorizationHandler(
(String) conf.get(DaemonConfig.NIMBUS_AUTHORIZER), conf);
} catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) {
LOG.error("Error initializing AuthorizedUserFilter: ", e);
throw new RuntimeException(e);
}
}
/**
* makeResponse.
* @param ex ex
* @param request request
* @param statusCode statusCode
* @return error response
*/
public static Response makeResponse(Exception ex, ContainerRequestContext request, int statusCode) {
String callback = null;
if (request.getMediaType() != null && request.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
try {
String json = IOUtils.toString(request.getEntityStream(), Charsets.UTF_8);
InputStream in = IOUtils.toInputStream(json);
request.setEntityStream(in);
Map<String, Object> requestBody = (Map<String, Object>) JSONValue.parse(json);
if (requestBody.containsKey(StormApiResource.callbackParameterName)) {
callback = String.valueOf(requestBody.get(StormApiResource.callbackParameterName));
}
} catch (IOException e) {
LOG.error("Exception while trying to get callback ", e);
}
}
return new JsonResponseBuilder().setData(
UIHelpers.exceptionToJson(ex, statusCode)).setCallback(callback)
.setStatus(statusCode).build();
}
@Override
public void filter(ContainerRequestContext containerRequestContext) {
AuthNimbusOp annotation = resourceInfo.getResourceMethod().getAnnotation(AuthNimbusOp.class);
if (annotation == null) {
return;
}
String op = annotation.value();
if (op == null) {
return;
}
Map topoConf = null;
if (annotation.needsTopoId()) {
final String topoId = containerRequestContext.getUriInfo().getPathParameters().get("id").get(0);
try (NimbusClient nimbusClient = NimbusClient.getConfiguredClient(conf)) {
topoConf = (Map) JSONValue.parse(nimbusClient.getClient().getTopologyConf(topoId));
} catch (AuthorizationException ae) {
LOG.error("Nimbus isn't allowing {} to access the topology conf of {}. {}", ReqContext.context(), topoId, ae.get_msg());
containerRequestContext.abortWith(makeResponse(ae, containerRequestContext, 403));
return;
} catch (TException e) {
LOG.error("Unable to fetch topo conf for {} due to ", topoId, e);
containerRequestContext.abortWith(
makeResponse(new IOException("Unable to fetch topo conf for topo id " + topoId, e),
containerRequestContext, 500)
);
return;
}
}
ReqContext reqContext = ReqContext.context();
if (reqContext.isImpersonating()) {
if (uiImpersonationHandler != null) {
if (!uiImpersonationHandler.permit(reqContext, op, topoConf)) {
Principal realPrincipal = reqContext.realPrincipal();
Principal principal = reqContext.principal();
String user = "unknown";
if (principal != null) {
user = principal.getName();
}
String realUser = "unknown";
if (realPrincipal != null) {
realUser = realPrincipal.getName();
}
InetAddress remoteAddress = reqContext.remoteAddress();
containerRequestContext.abortWith(
makeResponse(new AuthorizationException(
"user '" + realUser + "' is not authorized to impersonate user '"
+ user + "' from host '" + remoteAddress.toString() + "'. Please"
+ "see SECURITY.MD to learn how to configure impersonation ACL."
), containerRequestContext, 401)
);
return;
}
LOG.warn(" principal {} is trying to impersonate {} but {} has no authorizer configured. "
+ "This is a potential security hole. Please see SECURITY.MD to learn how to "
+ "configure an impersonation authorizer.",
reqContext.realPrincipal().toString(), reqContext.principal().toString(),
conf.get(DaemonConfig.NIMBUS_IMPERSONATION_AUTHORIZER));
}
}
if (uiAclHandler != null) {
if (!uiAclHandler.permit(reqContext, op, topoConf)) {
Principal principal = reqContext.principal();
String user = "unknown";
if (principal != null) {
user = principal.getName();
}
containerRequestContext.abortWith(
makeResponse(new AuthorizationException("UI request '" + op + "' for '"
+ user + "' user is not authorized"),
containerRequestContext, 403)
);
return;
}
}
}
}