/* * Copyright © 2015-2016 Cask Data, Inc. * * Licensed 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 co.cask.cdap.gateway.handlers; import co.cask.cdap.common.BadRequestException; import co.cask.cdap.common.FeatureDisabledException; import co.cask.cdap.common.NotFoundException; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.entity.EntityExistenceVerifier; import co.cask.cdap.common.logging.AuditLogEntry; import co.cask.cdap.gateway.handlers.util.AbstractAppFabricHttpHandler; import co.cask.cdap.proto.codec.EntityIdTypeAdapter; import co.cask.cdap.proto.id.EntityId; import co.cask.cdap.proto.security.Action; import co.cask.cdap.proto.security.AuthorizationRequest; import co.cask.cdap.proto.security.GrantRequest; import co.cask.cdap.proto.security.Principal; import co.cask.cdap.proto.security.Privilege; import co.cask.cdap.proto.security.RevokeRequest; import co.cask.cdap.proto.security.Role; import co.cask.cdap.security.authorization.AuthorizerInstantiatorService; import co.cask.cdap.security.spi.authentication.SecurityRequestContext; import co.cask.cdap.security.spi.authorization.Authorizer; import co.cask.http.HttpResponder; import com.google.common.base.Objects; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.inject.Inject; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Type; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.EnumSet; import java.util.Set; import javax.annotation.Nullable; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; /** * Exposes {@link Authorizer} operations via HTTP. */ @Path(Constants.Gateway.API_VERSION_3 + "/security/authorization") public class AuthorizationHandler extends AbstractAppFabricHttpHandler { private static final Logger AUDIT_LOG = LoggerFactory.getLogger("authorization-access"); private final AuthorizerInstantiatorService authorizerInstantiatorService; private static final Gson GSON = new GsonBuilder() .registerTypeAdapter(EntityId.class, new EntityIdTypeAdapter()) .create(); private static final Type PRIVILEGE_SET_TYPE = new TypeToken<Set<Privilege>>() { }.getType(); private final boolean authenticationEnabled; private final boolean authorizationEnabled; private final EntityExistenceVerifier entityExistenceVerifier; @Inject AuthorizationHandler(AuthorizerInstantiatorService authorizerInstantiatorService, CConfiguration cConf, EntityExistenceVerifier entityExistenceVerifier) { this.authorizerInstantiatorService = authorizerInstantiatorService; this.authenticationEnabled = cConf.getBoolean(Constants.Security.ENABLED); this.authorizationEnabled = cConf.getBoolean(Constants.Security.Authorization.ENABLED); this.entityExistenceVerifier = entityExistenceVerifier; } @Path("/privileges/grant") @POST public void grant(HttpRequest httpRequest, HttpResponder httpResponder) throws Exception { ensureSecurityEnabled(); GrantRequest request = parseBody(httpRequest, GrantRequest.class); verifyAuthRequest(request); Set<Action> actions = request.getActions() == null ? EnumSet.allOf(Action.class) : request.getActions(); // enforce that the user granting access has admin privileges on the entity authorizerInstantiatorService.get().enforce(request.getEntity(), SecurityRequestContext.toPrincipal(), Action.ADMIN); authorizerInstantiatorService.get().grant(request.getEntity(), request.getPrincipal(), actions); httpResponder.sendStatus(HttpResponseStatus.OK); createLogEntry(httpRequest, request, HttpResponseStatus.OK); } @Path("/privileges/revoke") @POST public void revoke(HttpRequest httpRequest, HttpResponder httpResponder) throws Exception { ensureSecurityEnabled(); RevokeRequest request = parseBody(httpRequest, RevokeRequest.class); verifyAuthRequest(request); // enforce that the user revoking access has admin privileges on the entity authorizerInstantiatorService.get().enforce(request.getEntity(), SecurityRequestContext.toPrincipal(), Action.ADMIN); if (request.getPrincipal() == null && request.getActions() == null) { authorizerInstantiatorService.get().revoke(request.getEntity()); } else { Set<Action> actions = request.getActions() == null ? EnumSet.allOf(Action.class) : request.getActions(); authorizerInstantiatorService.get().revoke(request.getEntity(), request.getPrincipal(), actions); } httpResponder.sendStatus(HttpResponseStatus.OK); createLogEntry(httpRequest, request, HttpResponseStatus.OK); } @Path("{principal-type}/{principal-name}/privileges") @GET public void listPrivileges(HttpRequest httpRequest, HttpResponder httpResponder, @PathParam("principal-type") String principalType, @PathParam("principal-name") String principalName) throws Exception { ensureSecurityEnabled(); Principal principal = new Principal(principalName, Principal.PrincipalType.valueOf(principalType.toUpperCase())); httpResponder.sendJson(HttpResponseStatus.OK, authorizerInstantiatorService.get().listPrivileges(principal), PRIVILEGE_SET_TYPE, GSON); createLogEntry(httpRequest, null, HttpResponseStatus.OK); } /******************************************************************************************************************** * Role Management : For Role Based Access Control ********************************************************************************************************************/ @Path("/roles/{role-name}") @PUT public void createRole(HttpRequest httpRequest, HttpResponder httpResponder, @PathParam("role-name") String roleName) throws Exception { ensureSecurityEnabled(); authorizerInstantiatorService.get().createRole(new Role(roleName)); httpResponder.sendStatus(HttpResponseStatus.OK); createLogEntry(httpRequest, null, HttpResponseStatus.OK); } @Path("/roles/{role-name}") @DELETE public void dropRole(HttpRequest httpRequest, HttpResponder httpResponder, @PathParam("role-name") String roleName) throws Exception { ensureSecurityEnabled(); authorizerInstantiatorService.get().dropRole(new Role(roleName)); httpResponder.sendStatus(HttpResponseStatus.OK); createLogEntry(httpRequest, null, HttpResponseStatus.OK); } @Path("/roles") @GET public void listAllRoles(HttpRequest httpRequest, HttpResponder httpResponder) throws Exception { ensureSecurityEnabled(); httpResponder.sendJson(HttpResponseStatus.OK, authorizerInstantiatorService.get().listAllRoles()); createLogEntry(httpRequest, null, HttpResponseStatus.OK); } @Path("{principal-type}/{principal-name}/roles") @GET public void listRoles(HttpRequest httpRequest, HttpResponder httpResponder, @PathParam("principal-type") String principalType, @PathParam("principal-name") String principalName) throws Exception { ensureSecurityEnabled(); Principal principal = new Principal(principalName, Principal.PrincipalType.valueOf(principalType.toUpperCase())); httpResponder.sendJson(HttpResponseStatus.OK, authorizerInstantiatorService.get().listRoles(principal)); createLogEntry(httpRequest, null, HttpResponseStatus.OK); } @Path("/{principal-type}/{principal-name}/roles/{role-name}") @PUT public void addRoleToPrincipal(HttpRequest httpRequest, HttpResponder httpResponder, @PathParam("principal-type") String principalType, @PathParam("principal-name") String principalName, @PathParam("role-name") String roleName) throws Exception { ensureSecurityEnabled(); authorizerInstantiatorService.get().addRoleToPrincipal(new Role(roleName), new Principal(principalName, Principal.PrincipalType.valueOf(principalType.toUpperCase()))); httpResponder.sendStatus(HttpResponseStatus.OK); createLogEntry(httpRequest, null, HttpResponseStatus.OK); } @Path("/{principal-type}/{principal-name}/roles/{role-name}") @DELETE public void removeRoleFromPrincipal(HttpRequest httpRequest, HttpResponder httpResponder, @PathParam("principal-type") String principalType, @PathParam("principal-name") String principalName, @PathParam("role-name") String roleName) throws Exception { ensureSecurityEnabled(); authorizerInstantiatorService.get().removeRoleFromPrincipal(new Role(roleName), new Principal(principalName, Principal.PrincipalType.valueOf(principalType.toUpperCase()))); httpResponder.sendStatus(HttpResponseStatus.OK); createLogEntry(httpRequest, null, HttpResponseStatus.OK); } private void ensureSecurityEnabled() throws FeatureDisabledException { if (!authenticationEnabled) { throw new FeatureDisabledException(FeatureDisabledException.Feature.AUTHENTICATION, FeatureDisabledException.CDAP_SITE, Constants.Security.ENABLED, "true"); } if (!authorizationEnabled) { throw new FeatureDisabledException(FeatureDisabledException.Feature.AUTHORIZATION, FeatureDisabledException.CDAP_SITE, Constants.Security.Authorization.ENABLED, "true"); } } private void verifyAuthRequest(AuthorizationRequest request) throws BadRequestException, NotFoundException { if (request == null) { throw new BadRequestException("Missing request body"); } EntityId entity = request.getEntity(); entityExistenceVerifier.ensureExists(entity); } private void createLogEntry(HttpRequest httpRequest, @Nullable AuthorizationRequest request, HttpResponseStatus responseStatus) throws UnknownHostException { AuditLogEntry logEntry = new AuditLogEntry(); logEntry.setUserName(Objects.firstNonNull(SecurityRequestContext.getUserId(), "-")); logEntry.setClientIP(InetAddress.getByName(Objects.firstNonNull(SecurityRequestContext.getUserIP(), "0.0.0.0"))); logEntry.setRequestLine(httpRequest.getMethod(), httpRequest.getUri(), httpRequest.getProtocolVersion()); if (request != null) { logEntry.setRequestBody(String.format("[%s %s %s]", request.getPrincipal(), request.getEntity(), request.getActions())); } logEntry.setResponseCode(responseStatus.getCode()); AUDIT_LOG.trace(logEntry.toString()); } }