Java tutorial
/* * 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.nifi.registry.ranger; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; import org.apache.nifi.registry.properties.NiFiRegistryProperties; import org.apache.nifi.registry.security.authorization.AccessPolicy; import org.apache.nifi.registry.security.authorization.AccessPolicyProvider; import org.apache.nifi.registry.security.authorization.AccessPolicyProviderInitializationContext; import org.apache.nifi.registry.security.authorization.AuthorizationAuditor; import org.apache.nifi.registry.security.authorization.AuthorizationRequest; import org.apache.nifi.registry.security.authorization.AuthorizationResult; import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext; import org.apache.nifi.registry.security.authorization.AuthorizerInitializationContext; import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider; import org.apache.nifi.registry.security.authorization.ManagedAuthorizer; import org.apache.nifi.registry.security.authorization.RequestAction; import org.apache.nifi.registry.security.authorization.UserContextKeys; import org.apache.nifi.registry.security.authorization.UserGroupProvider; import org.apache.nifi.registry.security.authorization.UserGroupProviderLookup; import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext; import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException; import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException; import org.apache.nifi.registry.security.exception.SecurityProviderCreationException; import org.apache.nifi.registry.util.PropertyValue; import org.apache.ranger.audit.model.AuthzAuditEvent; import org.apache.ranger.authorization.hadoop.config.RangerConfiguration; import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler; import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.net.MalformedURLException; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; /** * Authorizer implementation that uses Apache Ranger to make authorization decisions. */ public class RangerAuthorizer implements ManagedAuthorizer, AuthorizationAuditor { private static final Logger logger = LoggerFactory.getLogger(RangerAuthorizer.class); private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider"; static final String USER_GROUP_PROVIDER = "User Group Provider"; static final String RANGER_AUDIT_PATH_PROP = "Ranger Audit Config Path"; static final String RANGER_SECURITY_PATH_PROP = "Ranger Security Config Path"; static final String RANGER_KERBEROS_ENABLED_PROP = "Ranger Kerberos Enabled"; static final String RANGER_ADMIN_IDENTITY_PROP = "Ranger Admin Identity"; static final String RANGER_SERVICE_TYPE_PROP = "Ranger Service Type"; static final String RANGER_APP_ID_PROP = "Ranger Application Id"; static final String RANGER_NIFI_REG_RESOURCE_NAME = "nifi-registry-resource"; private static final String DEFAULT_SERVICE_TYPE = "nifi-registry"; private static final String DEFAULT_APP_ID = "nifi-registry"; static final String RESOURCES_RESOURCE = "/policies"; static final String HADOOP_SECURITY_AUTHENTICATION = "hadoop.security.authentication"; private static final String KERBEROS_AUTHENTICATION = "kerberos"; private final Map<AuthorizationRequest, RangerAccessResult> resultLookup = new WeakHashMap<>(); private volatile RangerBasePluginWithPolicies rangerPlugin = null; private volatile RangerDefaultAuditHandler defaultAuditHandler = null; private volatile String rangerAdminIdentity = null; private volatile NiFiRegistryProperties registryProperties; private UserGroupProviderLookup userGroupProviderLookup; private UserGroupProvider userGroupProvider; @Override public void initialize(AuthorizerInitializationContext initializationContext) throws SecurityProviderCreationException { userGroupProviderLookup = initializationContext.getUserGroupProviderLookup(); } @Override public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException { final String userGroupProviderKey = configurationContext.getProperty(USER_GROUP_PROVIDER).getValue(); if (StringUtils.isEmpty(userGroupProviderKey)) { throw new SecurityProviderCreationException(USER_GROUP_PROVIDER + " must be specified."); } userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderKey); // ensure the desired access policy provider has a user group provider if (userGroupProvider == null) { throw new SecurityProviderCreationException( String.format("Unable to locate configured User Group Provider: %s", userGroupProviderKey)); } try { if (rangerPlugin == null) { logger.info("initializing base plugin"); final PropertyValue securityConfigValue = configurationContext .getProperty(RANGER_SECURITY_PATH_PROP); addRequiredResource(RANGER_SECURITY_PATH_PROP, securityConfigValue); final PropertyValue auditConfigValue = configurationContext.getProperty(RANGER_AUDIT_PATH_PROP); addRequiredResource(RANGER_AUDIT_PATH_PROP, auditConfigValue); boolean rangerKerberosEnabled = Boolean.valueOf(getConfigValue(configurationContext, RANGER_KERBEROS_ENABLED_PROP, Boolean.FALSE.toString())); if (rangerKerberosEnabled) { // configure UGI for when RangerAdminRESTClient calls UserGroupInformation.isSecurityEnabled() final Configuration securityConf = new Configuration(); securityConf.set(HADOOP_SECURITY_AUTHENTICATION, KERBEROS_AUTHENTICATION); UserGroupInformation.setConfiguration(securityConf); // login with the nifi registry principal and keytab, RangerAdminRESTClient will use Ranger's MiscUtil which // will grab UserGroupInformation.getLoginUser() and call ugi.checkTGTAndReloginFromKeytab(); final String registryPrincipal = registryProperties.getKerberosServicePrincipal(); final String registryKeytab = registryProperties.getKerberosServiceKeytabLocation(); if (StringUtils.isBlank(registryPrincipal) || StringUtils.isBlank(registryKeytab)) { throw new SecurityProviderCreationException( "Principal and Keytab must be provided when Kerberos is enabled"); } UserGroupInformation.loginUserFromKeytab(registryPrincipal.trim(), registryKeytab.trim()); } final String serviceType = getConfigValue(configurationContext, RANGER_SERVICE_TYPE_PROP, DEFAULT_SERVICE_TYPE); final String appId = getConfigValue(configurationContext, RANGER_APP_ID_PROP, DEFAULT_APP_ID); rangerPlugin = createRangerBasePlugin(serviceType, appId); rangerPlugin.init(); defaultAuditHandler = new RangerDefaultAuditHandler(); rangerAdminIdentity = getConfigValue(configurationContext, RANGER_ADMIN_IDENTITY_PROP, null); } else { logger.info("base plugin already initialized"); } } catch (Throwable t) { throw new SecurityProviderCreationException("Error creating RangerBasePlugin", t); } } protected RangerBasePluginWithPolicies createRangerBasePlugin(final String serviceType, final String appId) { return new RangerBasePluginWithPolicies(serviceType, appId, userGroupProvider); } @Override public AuthorizationResult authorize(final AuthorizationRequest request) throws SecurityProviderCreationException { final String identity = request.getIdentity(); final Set<String> userGroups = request.getGroups(); final String resourceIdentifier = request.getResource().getIdentifier(); // if a ranger admin identity was provided, and it equals the identity making the request, // and the request is to retrieve the resources, then allow it through if (StringUtils.isNotBlank(rangerAdminIdentity) && rangerAdminIdentity.equals(identity) && resourceIdentifier.equals(RESOURCES_RESOURCE)) { return AuthorizationResult.approved(); } final String clientIp; if (request.getUserContext() != null) { clientIp = request.getUserContext().get(UserContextKeys.CLIENT_ADDRESS.name()); } else { clientIp = null; } final RangerAccessResourceImpl resource = new RangerAccessResourceImpl(); resource.setValue(RANGER_NIFI_REG_RESOURCE_NAME, resourceIdentifier); final RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl(); rangerRequest.setResource(resource); rangerRequest.setAction(request.getAction().name()); rangerRequest.setAccessType(request.getAction().name()); rangerRequest.setUser(identity); rangerRequest.setUserGroups(userGroups); rangerRequest.setAccessTime(new Date()); if (!StringUtils.isBlank(clientIp)) { rangerRequest.setClientIPAddress(clientIp); } final RangerAccessResult result = rangerPlugin.isAccessAllowed(rangerRequest); // store the result for auditing purposes later if appropriate if (request.isAccessAttempt()) { synchronized (resultLookup) { resultLookup.put(request, result); } } if (result != null && result.getIsAllowed()) { // return approved return AuthorizationResult.approved(); } else { // if result.getIsAllowed() is false, then we need to determine if it was because no policy exists for the // given resource, or if it was because a policy exists but not for the given user or action final boolean doesPolicyExist = rangerPlugin.doesPolicyExist(request.getResource().getIdentifier(), request.getAction()); if (doesPolicyExist) { final String reason = result == null ? null : result.getReason(); if (reason != null) { logger.debug(String.format("Unable to authorize %s due to %s", identity, reason)); } // a policy does exist for the resource so we were really denied access here return AuthorizationResult.denied(request.getExplanationSupplier().get()); } else { // a policy doesn't exist so return resource not found so NiFi Registry can work back up the resource hierarchy return AuthorizationResult.resourceNotFound(); } } } @Override public void auditAccessAttempt(final AuthorizationRequest request, final AuthorizationResult result) { final RangerAccessResult rangerResult; synchronized (resultLookup) { rangerResult = resultLookup.remove(request); } if (rangerResult != null && rangerResult.getIsAudited()) { AuthzAuditEvent event = defaultAuditHandler.getAuthzEvents(rangerResult); // update the event with the originally requested resource event.setResourceType(RANGER_NIFI_REG_RESOURCE_NAME); event.setResourcePath(request.getRequestedResource().getIdentifier()); defaultAuditHandler.logAuthzAudit(event); } } @Override public void preDestruction() throws SecurityProviderCreationException { if (rangerPlugin != null) { try { rangerPlugin.cleanup(); rangerPlugin = null; } catch (Throwable t) { throw new SecurityProviderCreationException("Error cleaning up RangerBasePlugin", t); } } } @AuthorizerContext public void setRegistryProperties(final NiFiRegistryProperties properties) { this.registryProperties = properties; } /** * Adds a resource to the RangerConfiguration singleton so it is already there by the time RangerBasePlugin.init() * is called. * * @param name the name of the given PropertyValue from the AuthorizationConfigurationContext * @param resourceValue the value for the given name, should be a full path to a file */ private void addRequiredResource(final String name, final PropertyValue resourceValue) { if (resourceValue == null || StringUtils.isBlank(resourceValue.getValue())) { throw new SecurityProviderCreationException(name + " must be specified."); } final File resourceFile = new File(resourceValue.getValue()); if (!resourceFile.exists() || !resourceFile.canRead()) { throw new SecurityProviderCreationException(resourceValue + " does not exist, or can not be read"); } try { RangerConfiguration.getInstance().addResource(resourceFile.toURI().toURL()); } catch (MalformedURLException e) { throw new SecurityProviderCreationException("Error creating URI for " + resourceValue, e); } } private String getConfigValue(final AuthorizerConfigurationContext context, final String name, final String defaultValue) { final PropertyValue configValue = context.getProperty(name); String retValue = defaultValue; if (configValue != null && !StringUtils.isBlank(configValue.getValue())) { retValue = configValue.getValue(); } return retValue; } @Override public String getFingerprint() throws AuthorizationAccessException { final StringWriter out = new StringWriter(); try { // create the document final DocumentBuilder documentBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); final Document document = documentBuilder.newDocument(); // create the root element final Element managedRangerAuthorizationsElement = document .createElement("managedRangerAuthorizations"); document.appendChild(managedRangerAuthorizationsElement); // create the user group provider element final Element userGroupProviderElement = document.createElement(USER_GROUP_PROVIDER_ELEMENT); managedRangerAuthorizationsElement.appendChild(userGroupProviderElement); // append fingerprint if the provider is configurable if (userGroupProvider instanceof ConfigurableUserGroupProvider) { userGroupProviderElement.appendChild(document .createTextNode(((ConfigurableUserGroupProvider) userGroupProvider).getFingerprint())); } final Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(new DOMSource(document), new StreamResult(out)); } catch (ParserConfigurationException | TransformerException e) { throw new AuthorizationAccessException("Unable to generate fingerprint", e); } return out.toString(); } private String parseFingerprint(final String fingerprint) throws AuthorizationAccessException { final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8); try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) { final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder(); final Document document = docBuilder.parse(in); final Element rootElement = document.getDocumentElement(); final NodeList userGroupProviderList = rootElement.getElementsByTagName(USER_GROUP_PROVIDER_ELEMENT); if (userGroupProviderList.getLength() != 1) { throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", USER_GROUP_PROVIDER_ELEMENT, fingerprint)); } final Node userGroupProvider = userGroupProviderList.item(0); return userGroupProvider.getTextContent(); } catch (SAXException | ParserConfigurationException | IOException e) { throw new AuthorizationAccessException("Unable to parse fingerprint", e); } } @Override public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { if (StringUtils.isBlank(fingerprint)) { return; } final String userGroupFingerprint = parseFingerprint(fingerprint); if (StringUtils.isNotBlank(userGroupFingerprint) && userGroupProvider instanceof ConfigurableUserGroupProvider) { ((ConfigurableUserGroupProvider) userGroupProvider).inheritFingerprint(userGroupFingerprint); } } @Override public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { final String userGroupFingerprint = parseFingerprint(proposedFingerprint); if (StringUtils.isNotBlank(userGroupFingerprint)) { if (userGroupProvider instanceof ConfigurableUserGroupProvider) { ((ConfigurableUserGroupProvider) userGroupProvider).checkInheritability(userGroupFingerprint); } else { throw new UninheritableAuthorizationsException( "User/Group fingerprint is not blank and the configured UserGroupProvider does not support fingerprinting."); } } } @Override public AccessPolicyProvider getAccessPolicyProvider() { return new AccessPolicyProvider() { @Override public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException { return rangerPlugin.getAccessPolicies(); } @Override public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { return rangerPlugin.getAccessPolicy(identifier); } @Override public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException { return rangerPlugin.getAccessPolicy(resourceIdentifier, action); } @Override public UserGroupProvider getUserGroupProvider() { return userGroupProvider; } @Override public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws SecurityProviderCreationException { } @Override public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException { } @Override public void preDestruction() throws SecurityProviderCreationException { } }; } }