NIFI-4925:

- Addressing memory leak from lingering authorization results that did not represent actual access attempts. This closes #2511.

Signed-off-by: Mark Payne <markap14@hotmail.com>
This commit is contained in:
Matt Gilman 2018-03-02 16:24:34 -05:00 committed by Mark Payne
parent e916594b69
commit 91f40febeb
5 changed files with 83 additions and 14 deletions

View File

@ -0,0 +1,47 @@
/*
* 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.authorization;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AuthorizationAuditorInvocationHandler implements InvocationHandler {
private final Authorizer authorizer;
private final AuthorizationAuditor auditor;
public AuthorizationAuditorInvocationHandler(final Authorizer authorizer, final AuthorizationAuditor auditor) {
this.authorizer = authorizer;
this.auditor = auditor;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
try {
if (AuthorizationAuditor.class.getMethod("auditAccessAttempt", AuthorizationRequest.class, AuthorizationResult.class).equals(method)) {
return method.invoke(auditor, args);
} else {
return method.invoke(authorizer, args);
}
} catch (final InvocationTargetException e) {
// If the proxied instance throws an Exception, it'll be wrapped in an InvocationTargetException. We want
// to instead re-throw what the proxied instance threw, so we pull it out of the InvocationTargetException.
throw e.getCause();
}
}
}

View File

@ -101,9 +101,11 @@ public final class AuthorizerFactory {
} }
public static Authorizer installIntegrityChecks(final Authorizer baseAuthorizer) { public static Authorizer installIntegrityChecks(final Authorizer baseAuthorizer) {
Authorizer authorizer;
if (baseAuthorizer instanceof ManagedAuthorizer) { if (baseAuthorizer instanceof ManagedAuthorizer) {
final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer; final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer;
return new ManagedAuthorizer() { authorizer = new ManagedAuthorizer() {
@Override @Override
public String getFingerprint() throws AuthorizationAccessException { public String getFingerprint() throws AuthorizationAccessException {
return baseManagedAuthorizer.getFingerprint(); return baseManagedAuthorizer.getFingerprint();
@ -395,7 +397,7 @@ public final class AuthorizerFactory {
} }
}; };
} else { } else {
return new Authorizer() { authorizer = new Authorizer() {
@Override @Override
public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
final AuthorizationResult result = baseAuthorizer.authorize(request); final AuthorizationResult result = baseAuthorizer.authorize(request);
@ -422,6 +424,19 @@ public final class AuthorizerFactory {
} }
}; };
} }
// conditionally add support for the audit methods
if (baseAuthorizer instanceof AuthorizationAuditor) {
final AuthorizationAuditorInvocationHandler invocationHandler = new AuthorizationAuditorInvocationHandler(authorizer, (AuthorizationAuditor) baseAuthorizer);
final List<Class<?>> interfaceList = ClassUtils.getAllInterfaces(authorizer.getClass());
interfaceList.add(AuthorizationAuditor.class);
final Class<?>[] interfaces = interfaceList.toArray(new Class<?>[interfaceList.size()]);
authorizer = (Authorizer) Proxy.newProxyInstance(authorizer.getClass().getClassLoader(), interfaces, invocationHandler);
}
return authorizer;
} }
/** /**
@ -433,7 +448,6 @@ public final class AuthorizerFactory {
public static Authorizer withNarLoader(final Authorizer baseAuthorizer, final ClassLoader classLoader) { public static Authorizer withNarLoader(final Authorizer baseAuthorizer, final ClassLoader classLoader) {
final AuthorizerInvocationHandler invocationHandler = new AuthorizerInvocationHandler(baseAuthorizer, classLoader); final AuthorizerInvocationHandler invocationHandler = new AuthorizerInvocationHandler(baseAuthorizer, classLoader);
// extract all interfaces... baseAuthorizer is non null so getAllInterfaces is non null
final List<Class<?>> interfaceList = ClassUtils.getAllInterfaces(baseAuthorizer.getClass()); final List<Class<?>> interfaceList = ClassUtils.getAllInterfaces(baseAuthorizer.getClass());
final Class<?>[] interfaces = interfaceList.toArray(new Class<?>[interfaceList.size()]); final Class<?>[] interfaces = interfaceList.toArray(new Class<?>[interfaceList.size()]);

View File

@ -172,6 +172,8 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserG
// ensure it was found // ensure it was found
if (authorizer == null) { if (authorizer == null) {
throw new Exception(String.format("The specified authorizer '%s' could not be found.", authorizerIdentifier)); throw new Exception(String.format("The specified authorizer '%s' could not be found.", authorizerIdentifier));
} else {
authorizer = AuthorizerFactory.installIntegrityChecks(authorizer);
} }
} }
} }
@ -350,7 +352,7 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserG
authorizerClassLoader = new URLClassLoader(urls, authorizerClassLoader); authorizerClassLoader = new URLClassLoader(urls, authorizerClassLoader);
} }
return AuthorizerFactory.installIntegrityChecks(AuthorizerFactory.withNarLoader(instance, authorizerClassLoader)); return AuthorizerFactory.withNarLoader(instance, authorizerClassLoader);
} }
private AuthorizerConfigurationContext loadAuthorizerConfiguration(final String identifier, final List<Property> properties) { private AuthorizerConfigurationContext loadAuthorizerConfiguration(final String identifier, final List<Property> properties) {

View File

@ -289,6 +289,8 @@ public class AuthorizerFactoryTest {
Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(mockAuthorizer); Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(mockAuthorizer);
authorizer.onConfigured(context); authorizer.onConfigured(context);
assertTrue(authorizer instanceof AuthorizationAuditor);
final AuthorizationRequest accessAttempt = new AuthorizationRequest.Builder() final AuthorizationRequest accessAttempt = new AuthorizationRequest.Builder()
.resource(new MockResource("resource1", "Resource 1")) .resource(new MockResource("resource1", "Resource 1"))
.identity("user-1") .identity("user-1")

View File

@ -46,9 +46,9 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.util.Date; import java.util.Date;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentMap;
/** /**
* Authorizer implementation that uses Apache Ranger to make authorization decisions. * Authorizer implementation that uses Apache Ranger to make authorization decisions.
@ -71,7 +71,7 @@ public class RangerNiFiAuthorizer implements Authorizer, AuthorizationAuditor {
static final String HADOOP_SECURITY_AUTHENTICATION = "hadoop.security.authentication"; static final String HADOOP_SECURITY_AUTHENTICATION = "hadoop.security.authentication";
static final String KERBEROS_AUTHENTICATION = "kerberos"; static final String KERBEROS_AUTHENTICATION = "kerberos";
private final ConcurrentMap<AuthorizationRequest, RangerAccessResult> resultLookup = new ConcurrentHashMap<>(); private final Map<AuthorizationRequest, RangerAccessResult> resultLookup = new WeakHashMap<>();
private volatile RangerBasePluginWithPolicies nifiPlugin = null; private volatile RangerBasePluginWithPolicies nifiPlugin = null;
private volatile RangerDefaultAuditHandler defaultAuditHandler = null; private volatile RangerDefaultAuditHandler defaultAuditHandler = null;
@ -175,10 +175,14 @@ public class RangerNiFiAuthorizer implements Authorizer, AuthorizationAuditor {
final RangerAccessResult result = nifiPlugin.isAccessAllowed(rangerRequest); final RangerAccessResult result = nifiPlugin.isAccessAllowed(rangerRequest);
if (result != null && result.getIsAllowed()) { // store the result for auditing purposes later if appropriate
// store the result for auditing purposes later if (request.isAccessAttempt()) {
resultLookup.put(request, result); synchronized (resultLookup) {
resultLookup.put(request, result);
}
}
if (result != null && result.getIsAllowed()) {
// return approved // return approved
return AuthorizationResult.approved(); return AuthorizationResult.approved();
} else { } else {
@ -192,9 +196,6 @@ public class RangerNiFiAuthorizer implements Authorizer, AuthorizationAuditor {
logger.debug(String.format("Unable to authorize %s due to %s", identity, reason)); logger.debug(String.format("Unable to authorize %s due to %s", identity, reason));
} }
// store the result for auditing purposes later
resultLookup.put(request, result);
// a policy does exist for the resource so we were really denied access here // a policy does exist for the resource so we were really denied access here
return AuthorizationResult.denied(request.getExplanationSupplier().get()); return AuthorizationResult.denied(request.getExplanationSupplier().get());
} else { } else {
@ -206,7 +207,10 @@ public class RangerNiFiAuthorizer implements Authorizer, AuthorizationAuditor {
@Override @Override
public void auditAccessAttempt(final AuthorizationRequest request, final AuthorizationResult result) { public void auditAccessAttempt(final AuthorizationRequest request, final AuthorizationResult result) {
final RangerAccessResult rangerResult = resultLookup.remove(request); final RangerAccessResult rangerResult;
synchronized (resultLookup) {
rangerResult = resultLookup.remove(request);
}
if (rangerResult != null && rangerResult.getIsAudited()) { if (rangerResult != null && rangerResult.getIsAudited()) {
AuthzAuditEvent event = defaultAuditHandler.getAuthzEvents(rangerResult); AuthzAuditEvent event = defaultAuditHandler.getAuthzEvents(rangerResult);