/* * 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.ranger.authorization; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; import org.apache.nifi.authorization.AuthorizationRequest; import org.apache.nifi.authorization.AuthorizationResult; import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.AuthorizerConfigurationContext; import org.apache.nifi.authorization.AuthorizerInitializationContext; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.Resource; import org.apache.nifi.authorization.UserContextKeys; import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.util.MockPropertyValue; import org.apache.nifi.util.NiFiProperties; import org.apache.ranger.plugin.policyengine.RangerAccessRequest; import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResult; import org.apache.ranger.plugin.policyengine.RangerAccessResultProcessor; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import javax.security.auth.login.LoginException; import java.io.File; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.notNull; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class TestRangerNiFiAuthorizer { private MockRangerNiFiAuthorizer authorizer; private RangerBasePluginWithPolicies rangerBasePlugin; private AuthorizerConfigurationContext configurationContext; private NiFiProperties nifiProperties; private final String serviceType = "nifiService"; private final String appId = "nifiAppId"; private RangerAccessResult allowedResult; private RangerAccessResult notAllowedResult; @Before public void setup() { // have to initialize this system property before anything else File krb5conf = new File("src/test/resources/krb5.conf"); assertTrue(krb5conf.exists()); System.setProperty("java.security.krb5.conf", krb5conf.getAbsolutePath()); // rest the authentication to simple in case any tests set it to kerberos final Configuration securityConf = new Configuration(); securityConf.set(RangerNiFiAuthorizer.HADOOP_SECURITY_AUTHENTICATION, "simple"); UserGroupInformation.setConfiguration(securityConf); configurationContext = createMockConfigContext(); rangerBasePlugin = Mockito.mock(RangerBasePluginWithPolicies.class); authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin); authorizer.onConfigured(configurationContext); assertFalse(UserGroupInformation.isSecurityEnabled()); allowedResult = Mockito.mock(RangerAccessResult.class); when(allowedResult.getIsAllowed()).thenReturn(true); notAllowedResult = Mockito.mock(RangerAccessResult.class); when(notAllowedResult.getIsAllowed()).thenReturn(false); } private AuthorizerConfigurationContext createMockConfigContext() { AuthorizerConfigurationContext configurationContext = Mockito.mock(AuthorizerConfigurationContext.class); when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_SECURITY_PATH_PROP))) .thenReturn(new MockPropertyValue("src/test/resources/ranger/ranger-nifi-security.xml")); when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_AUDIT_PATH_PROP))) .thenReturn(new MockPropertyValue("src/test/resources/ranger/ranger-nifi-audit.xml")); when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_APP_ID_PROP))) .thenReturn(new MockPropertyValue(appId)); when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_SERVICE_TYPE_PROP))) .thenReturn(new MockPropertyValue(serviceType)); return configurationContext; } @Test public void testOnConfigured() { verify(rangerBasePlugin, times(1)).init(); assertEquals(appId, authorizer.mockRangerBasePlugin.getAppId()); assertEquals(serviceType, authorizer.mockRangerBasePlugin.getServiceType()); } @Test public void testKerberosEnabledWithoutKeytab() { when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_KERBEROS_ENABLED_PROP))) .thenReturn(new MockPropertyValue("true")); nifiProperties = Mockito.mock(NiFiProperties.class); when(nifiProperties.getKerberosServicePrincipal()).thenReturn(""); authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin); authorizer.setNiFiProperties(nifiProperties); try { authorizer.onConfigured(configurationContext); Assert.fail("Should have thrown exception"); } catch (AuthorizerCreationException e) { // want to make sure this exception is from our authorizer code verifyOnlyAuthorizeCreationExceptions(e); } } @Test public void testKerberosEnabledWithoutPrincipal() { when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_KERBEROS_ENABLED_PROP))) .thenReturn(new MockPropertyValue("true")); nifiProperties = Mockito.mock(NiFiProperties.class); when(nifiProperties.getKerberosServiceKeytabLocation()).thenReturn(""); authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin); authorizer.setNiFiProperties(nifiProperties); try { authorizer.onConfigured(configurationContext); Assert.fail("Should have thrown exception"); } catch (AuthorizerCreationException e) { // want to make sure this exception is from our authorizer code verifyOnlyAuthorizeCreationExceptions(e); } } @Test public void testKerberosEnabledWithoutKeytabOrPrincipal() { when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_KERBEROS_ENABLED_PROP))) .thenReturn(new MockPropertyValue("true")); nifiProperties = Mockito.mock(NiFiProperties.class); when(nifiProperties.getKerberosServiceKeytabLocation()).thenReturn(""); when(nifiProperties.getKerberosServicePrincipal()).thenReturn(""); authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin); authorizer.setNiFiProperties(nifiProperties); try { authorizer.onConfigured(configurationContext); Assert.fail("Should have thrown exception"); } catch (AuthorizerCreationException e) { // want to make sure this exception is from our authorizer code verifyOnlyAuthorizeCreationExceptions(e); } } private void verifyOnlyAuthorizeCreationExceptions(AuthorizerCreationException e) { boolean foundOtherException = false; Throwable cause = e.getCause(); while (cause != null) { if (!(cause instanceof AuthorizerCreationException)) { foundOtherException = true; break; } cause = cause.getCause(); } assertFalse(foundOtherException); } @Test public void testKerberosEnabled() { when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_KERBEROS_ENABLED_PROP))) .thenReturn(new MockPropertyValue("true")); nifiProperties = Mockito.mock(NiFiProperties.class); when(nifiProperties.getKerberosServiceKeytabLocation()).thenReturn("test"); when(nifiProperties.getKerberosServicePrincipal()).thenReturn("test"); authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin); authorizer.setNiFiProperties(nifiProperties); try { authorizer.onConfigured(configurationContext); Assert.fail("Should have thrown exception"); } catch (AuthorizerCreationException e) { // getting a LoginException here means we attempted to login which is what we want boolean foundLoginException = false; Throwable cause = e.getCause(); while (cause != null) { if (cause instanceof LoginException) { foundLoginException = true; break; } cause = cause.getCause(); } assertTrue(foundLoginException); } } @Test public void testApprovedWithDirectAccess() { final String systemResource = "/system"; final RequestAction action = RequestAction.WRITE; final String user = "admin"; final String clientIp = "192.168.1.1"; final Map<String,String> userContext = new HashMap<>(); userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), clientIp); // the incoming NiFi request to test final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(new MockResource(systemResource, systemResource)) .action(action) .identity(user) .resourceContext(new HashMap<>()) .userContext(userContext) .accessAttempt(true) .anonymous(false) .build(); // the expected Ranger resource and request that are created final RangerAccessResourceImpl resource = new RangerAccessResourceImpl(); resource.setValue(RangerNiFiAuthorizer.RANGER_NIFI_RESOURCE_NAME, systemResource); final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl(); expectedRangerRequest.setResource(resource); expectedRangerRequest.setAction(request.getAction().name()); expectedRangerRequest.setAccessType(request.getAction().name()); expectedRangerRequest.setUser(request.getIdentity()); expectedRangerRequest.setClientIPAddress(clientIp); // a non-null result processor should be used for direct access when(rangerBasePlugin.isAccessAllowed( argThat(new RangerAccessRequestMatcher(expectedRangerRequest)), notNull(RangerAccessResultProcessor.class)) ).thenReturn(allowedResult); final AuthorizationResult result = authorizer.authorize(request); assertEquals(AuthorizationResult.approved().getResult(), result.getResult()); } @Test public void testApprovedWithNonDirectAccess() { final String systemResource = "/system"; final RequestAction action = RequestAction.WRITE; final String user = "admin"; // the incoming NiFi request to test final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(new MockResource(systemResource, systemResource)) .action(action) .identity(user) .resourceContext(new HashMap<>()) .accessAttempt(false) .anonymous(false) .build(); // the expected Ranger resource and request that are created final RangerAccessResourceImpl resource = new RangerAccessResourceImpl(); resource.setValue(RangerNiFiAuthorizer.RANGER_NIFI_RESOURCE_NAME, systemResource); final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl(); expectedRangerRequest.setResource(resource); expectedRangerRequest.setAction(request.getAction().name()); expectedRangerRequest.setAccessType(request.getAction().name()); expectedRangerRequest.setUser(request.getIdentity()); // no result processor should be provided used non-direct access when(rangerBasePlugin.isAccessAllowed( argThat(new RangerAccessRequestMatcher(expectedRangerRequest)), eq(null)) ).thenReturn(allowedResult); final AuthorizationResult result = authorizer.authorize(request); assertEquals(AuthorizationResult.approved().getResult(), result.getResult()); } @Test public void testResourceNotFound() { final String systemResource = "/system"; final RequestAction action = RequestAction.WRITE; final String user = "admin"; // the incoming NiFi request to test final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(new MockResource(systemResource, systemResource)) .action(action) .identity(user) .resourceContext(new HashMap<>()) .accessAttempt(true) .anonymous(false) .build(); // the expected Ranger resource and request that are created final RangerAccessResourceImpl resource = new RangerAccessResourceImpl(); resource.setValue(RangerNiFiAuthorizer.RANGER_NIFI_RESOURCE_NAME, systemResource); final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl(); expectedRangerRequest.setResource(resource); expectedRangerRequest.setAction(request.getAction().name()); expectedRangerRequest.setAccessType(request.getAction().name()); expectedRangerRequest.setUser(request.getIdentity()); // no result processor should be provided used non-direct access when(rangerBasePlugin.isAccessAllowed( argThat(new RangerAccessRequestMatcher(expectedRangerRequest)), notNull(RangerAccessResultProcessor.class)) ).thenReturn(notAllowedResult); // return false when checking if a policy exists for the resource when(rangerBasePlugin.doesPolicyExist(systemResource)).thenReturn(false); final AuthorizationResult result = authorizer.authorize(request); assertEquals(AuthorizationResult.resourceNotFound().getResult(), result.getResult()); } @Test public void testDenied() { final String systemResource = "/system"; final RequestAction action = RequestAction.WRITE; final String user = "admin"; // the incoming NiFi request to test final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(new MockResource(systemResource, systemResource)) .action(action) .identity(user) .resourceContext(new HashMap<>()) .accessAttempt(true) .anonymous(false) .build(); // the expected Ranger resource and request that are created final RangerAccessResourceImpl resource = new RangerAccessResourceImpl(); resource.setValue(RangerNiFiAuthorizer.RANGER_NIFI_RESOURCE_NAME, systemResource); final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl(); expectedRangerRequest.setResource(resource); expectedRangerRequest.setAction(request.getAction().name()); expectedRangerRequest.setAccessType(request.getAction().name()); expectedRangerRequest.setUser(request.getIdentity()); // no result processor should be provided used non-direct access when(rangerBasePlugin.isAccessAllowed( argThat(new RangerAccessRequestMatcher(expectedRangerRequest)), notNull(RangerAccessResultProcessor.class)) ).thenReturn(notAllowedResult); // return true when checking if a policy exists for the resource when(rangerBasePlugin.doesPolicyExist(systemResource)).thenReturn(true); final AuthorizationResult result = authorizer.authorize(request); assertEquals(AuthorizationResult.denied().getResult(), result.getResult()); } @Test public void testRangerAdminApproved() { runRangerAdminTest(RangerNiFiAuthorizer.RESOURCES_RESOURCE, AuthorizationResult.approved().getResult()); } @Test public void testRangerAdminDenied() { runRangerAdminTest("/flow", AuthorizationResult.denied().getResult()); } private void runRangerAdminTest(final String resourceIdentifier, final AuthorizationResult.Result expectedResult) { configurationContext = createMockConfigContext(); final String rangerAdminIdentity = "ranger-admin"; when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP))) .thenReturn(new MockPropertyValue(rangerAdminIdentity)); rangerBasePlugin = Mockito.mock(RangerBasePluginWithPolicies.class); authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin); authorizer.onConfigured(configurationContext); final RequestAction action = RequestAction.WRITE; // the incoming NiFi request to test final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(new MockResource(resourceIdentifier, resourceIdentifier)) .action(action) .identity(rangerAdminIdentity) .resourceContext(new HashMap<>()) .accessAttempt(true) .anonymous(false) .build(); // the expected Ranger resource and request that are created final RangerAccessResourceImpl resource = new RangerAccessResourceImpl(); resource.setValue(RangerNiFiAuthorizer.RANGER_NIFI_RESOURCE_NAME, resourceIdentifier); final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl(); expectedRangerRequest.setResource(resource); expectedRangerRequest.setAction(request.getAction().name()); expectedRangerRequest.setAccessType(request.getAction().name()); expectedRangerRequest.setUser(request.getIdentity()); // return true when checking if a policy exists for the resource when(rangerBasePlugin.doesPolicyExist(resourceIdentifier)).thenReturn(true); // a non-null result processor should be used for direct access when(rangerBasePlugin.isAccessAllowed( argThat(new RangerAccessRequestMatcher(expectedRangerRequest)), notNull(RangerAccessResultProcessor.class)) ).thenReturn(notAllowedResult); final AuthorizationResult result = authorizer.authorize(request); assertEquals(expectedResult, result.getResult()); } @Test @Ignore public void testIntegration() { final AuthorizerInitializationContext initializationContext = Mockito.mock(AuthorizerInitializationContext.class); final AuthorizerConfigurationContext configurationContext = Mockito.mock(AuthorizerConfigurationContext.class); when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_SECURITY_PATH_PROP))) .thenReturn(new MockPropertyValue("src/test/resources/ranger/ranger-nifi-security.xml")); when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_AUDIT_PATH_PROP))) .thenReturn(new MockPropertyValue("src/test/resources/ranger/ranger-nifi-audit.xml")); Authorizer authorizer = new RangerNiFiAuthorizer(); try { authorizer.initialize(initializationContext); authorizer.onConfigured(configurationContext); final AuthorizationRequest request = new AuthorizationRequest.Builder() .resource(new Resource() { @Override public String getIdentifier() { return "/system"; } @Override public String getName() { return "/system"; } @Override public String getSafeDescription() { return "system"; } }) .action(RequestAction.WRITE) .identity("admin") .resourceContext(new HashMap<>()) .accessAttempt(true) .anonymous(false) .build(); final AuthorizationResult result = authorizer.authorize(request); Assert.assertEquals(AuthorizationResult.denied().getResult(), result.getResult()); } finally { authorizer.preDestruction(); } } /** * Extend RangerNiFiAuthorizer to inject a mock base plugin for testing. */ private static class MockRangerNiFiAuthorizer extends RangerNiFiAuthorizer { RangerBasePluginWithPolicies mockRangerBasePlugin; public MockRangerNiFiAuthorizer(RangerBasePluginWithPolicies mockRangerBasePlugin) { this.mockRangerBasePlugin = mockRangerBasePlugin; } @Override protected RangerBasePluginWithPolicies createRangerBasePlugin(String serviceType, String appId) { when(mockRangerBasePlugin.getAppId()).thenReturn(appId); when(mockRangerBasePlugin.getServiceType()).thenReturn(serviceType); return mockRangerBasePlugin; } } /** * Resource implementation for testing. */ private static class MockResource implements Resource { private final String identifier; private final String name; public MockResource(String identifier, String name) { this.identifier = identifier; this.name = name; } @Override public String getIdentifier() { return identifier; } @Override public String getName() { return name; } @Override public String getSafeDescription() { return name; } } /** * Custom Mockito matcher for RangerAccessRequest objects. */ private static class RangerAccessRequestMatcher extends ArgumentMatcher<RangerAccessRequest> { private final RangerAccessRequest request; public RangerAccessRequestMatcher(RangerAccessRequest request) { this.request = request; } @Override public boolean matches(Object o) { if (!(o instanceof RangerAccessRequest)) { return false; } final RangerAccessRequest other = (RangerAccessRequest) o; final boolean clientIpsMatch = (other.getClientIPAddress() == null && request.getClientIPAddress() == null) || (other.getClientIPAddress() != null && request.getClientIPAddress() != null && other.getClientIPAddress().equals(request.getClientIPAddress())); return other.getResource().equals(request.getResource()) && other.getAccessType().equals(request.getAccessType()) && other.getAction().equals(request.getAction()) && other.getUser().equals(request.getUser()) && clientIpsMatch; } } }