/**
* 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.hadoop.gateway;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.hadoop.gateway.audit.api.Action;
import org.apache.hadoop.gateway.audit.api.ActionOutcome;
import org.apache.hadoop.gateway.audit.api.AuditContext;
import org.apache.hadoop.gateway.audit.api.AuditServiceFactory;
import org.apache.hadoop.gateway.audit.api.CorrelationContext;
import org.apache.hadoop.gateway.audit.api.ResourceType;
import org.apache.hadoop.gateway.audit.log4j.audit.AuditConstants;
import org.apache.hadoop.gateway.audit.log4j.audit.Log4jAuditService;
import org.apache.hadoop.gateway.audit.log4j.correlation.Log4jCorrelationService;
import org.apache.hadoop.gateway.config.GatewayConfig;
import org.apache.hadoop.gateway.dispatch.DefaultDispatch;
import org.apache.hadoop.gateway.i18n.resources.ResourcesFactory;
import org.apache.hadoop.test.log.CollectAppender;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.log4j.spi.LoggingEvent;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class AuditLoggingTest {
private static final String METHOD = "GET";
private static final String PATH = "path";
private static final String CONTEXT_PATH = "contextPath/";
private static final String ADDRESS = "address";
private static final String HOST = "host";
private static final GatewayResources RES = ResourcesFactory.get( GatewayResources.class );
@Before
public void loggingSetup() {
AuditServiceFactory.getAuditService().createContext();
CollectAppender.queue.clear();
}
@After
public void reset() {
AuditServiceFactory.getAuditService().detachContext();
}
@Test
/**
* Empty filter chain. Two events with same correlation ID are expected:
*
* action=access request_type=uri outcome=unavailable
* action=access request_type=uri outcome=success message=Response status: 404
*/
public void testNoFiltersAudit() throws ServletException, IOException {
FilterConfig config = EasyMock.createNiceMock( FilterConfig.class );
EasyMock.replay( config );
HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
ServletContext context = EasyMock.createNiceMock( ServletContext.class );
GatewayConfig gatewayConfig = EasyMock.createNiceMock( GatewayConfig.class );
EasyMock.expect( request.getMethod() ).andReturn( METHOD ).anyTimes();
EasyMock.expect( request.getPathInfo() ).andReturn( PATH ).anyTimes();
EasyMock.expect( request.getContextPath() ).andReturn( CONTEXT_PATH ).anyTimes();
EasyMock.expect( request.getRemoteAddr() ).andReturn( ADDRESS ).anyTimes();
EasyMock.expect( request.getRemoteHost() ).andReturn( HOST ).anyTimes();
EasyMock.expect( request.getServletContext() ).andReturn( context ).anyTimes();
EasyMock.expect( context.getAttribute(
GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gatewayConfig).anyTimes();
EasyMock.expect(gatewayConfig.getHeaderNameForRemoteAddress()).andReturn(
"Custom-Forwarded-For").anyTimes();
EasyMock.replay( request );
EasyMock.replay( context );
EasyMock.replay( gatewayConfig );
HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
EasyMock.replay( response );
FilterChain chain = EasyMock.createNiceMock( FilterChain.class );
EasyMock.replay( chain );
GatewayFilter gateway = new GatewayFilter();
gateway.init( config );
gateway.doFilter( request, response, chain );
gateway.destroy();
assertThat( CollectAppender.queue.size(), is( 1 ) );
Iterator<LoggingEvent> iterator = CollectAppender.queue.iterator();
LoggingEvent accessEvent = iterator.next();
verifyAuditEvent( accessEvent, CONTEXT_PATH + PATH, ResourceType.URI, Action.ACCESS, ActionOutcome.UNAVAILABLE, null, "Request method: GET" );
}
@Test
/**
* One NoOp filter in chain. Single audit event with same with specified request URI is expected:
*
* action=access request_type=uri outcome=unavailable
*/
public void testNoopFilter() throws ServletException, IOException,
URISyntaxException {
FilterConfig config = EasyMock.createNiceMock( FilterConfig.class );
EasyMock.replay( config );
HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
ServletContext context = EasyMock.createNiceMock( ServletContext.class );
GatewayConfig gatewayConfig = EasyMock.createNiceMock( GatewayConfig.class );
EasyMock.expect( request.getMethod() ).andReturn( METHOD ).anyTimes();
EasyMock.expect( request.getPathInfo() ).andReturn( PATH ).anyTimes();
EasyMock.expect( request.getContextPath() ).andReturn( CONTEXT_PATH ).anyTimes();
EasyMock.expect( request.getRemoteAddr() ).andReturn( ADDRESS ).anyTimes();
EasyMock.expect( request.getRemoteHost() ).andReturn( HOST ).anyTimes();
EasyMock.expect( request.getServletContext() ).andReturn( context ).anyTimes();
EasyMock.expect( context.getAttribute(
GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gatewayConfig).anyTimes();
EasyMock.expect(gatewayConfig.getHeaderNameForRemoteAddress()).andReturn(
"Custom-Forwarded-For").anyTimes();
EasyMock.replay( request );
EasyMock.replay( context );
EasyMock.replay( gatewayConfig );
HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
EasyMock.replay( response );
FilterChain chain = EasyMock.createNiceMock( FilterChain.class );
EasyMock.replay( chain );
Filter filter = EasyMock.createNiceMock( Filter.class );
EasyMock.replay( filter );
GatewayFilter gateway = new GatewayFilter();
gateway.addFilter( "path", "filter", filter, null, null );
gateway.init( config );
gateway.doFilter( request, response, chain );
gateway.destroy();
assertThat( CollectAppender.queue.size(), is( 1 ) );
Iterator<LoggingEvent> iterator = CollectAppender.queue.iterator();
LoggingEvent accessEvent = iterator.next();
verifyAuditEvent( accessEvent, CONTEXT_PATH + PATH, ResourceType.URI,
Action.ACCESS, ActionOutcome.UNAVAILABLE, null, "Request method: GET" );
}
@Test
/**
* Dispatching outbound request. Remote host is unreachable. Two log events is expected:
*
* action=dispatch request_type=uri outcome=FAILED
* action=dispatch request_type=uri outcome=unavailable
*/
public void testHttpClientOutboundException() throws IOException,
URISyntaxException {
String uri = "http://outbound-host:port/path";
HttpServletRequest inboundRequest = EasyMock.createNiceMock( HttpServletRequest.class );
EasyMock.expect( inboundRequest.getHeaderNames() ).andReturn( Collections.enumeration( new ArrayList<String>() ) ).anyTimes();
EasyMock.replay( inboundRequest );
HttpServletResponse outboundResponse = EasyMock.createNiceMock( HttpServletResponse.class );
EasyMock.replay( outboundResponse );
DefaultDispatch dispatch = new DefaultDispatch();
dispatch.setHttpClient(new DefaultHttpClient());
try {
dispatch.doGet( new URI( uri ), inboundRequest, outboundResponse );
fail( "Expected exception while accessing to unreachable host" );
} catch ( IOException e ) {
Iterator<LoggingEvent> iterator = CollectAppender.queue.iterator();
LoggingEvent unavailableEvent = iterator.next();
verifyValue( (String) unavailableEvent.getMDC( AuditConstants.MDC_RESOURCE_NAME_KEY ), uri );
verifyValue( (String) unavailableEvent.getMDC( AuditConstants.MDC_RESOURCE_TYPE_KEY ), ResourceType.URI );
verifyValue( (String) unavailableEvent.getMDC( AuditConstants.MDC_ACTION_KEY ), Action.DISPATCH );
verifyValue( (String) unavailableEvent.getMDC( AuditConstants.MDC_OUTCOME_KEY ), ActionOutcome.UNAVAILABLE );
LoggingEvent failureEvent = iterator.next();
verifyValue( (String) failureEvent.getMDC( AuditConstants.MDC_RESOURCE_NAME_KEY ), uri );
verifyValue( (String) failureEvent.getMDC( AuditConstants.MDC_RESOURCE_TYPE_KEY ), ResourceType.URI );
verifyValue( (String) failureEvent.getMDC( AuditConstants.MDC_ACTION_KEY ), Action.DISPATCH );
verifyValue( (String) failureEvent.getMDC( AuditConstants.MDC_OUTCOME_KEY ), ActionOutcome.FAILURE );
}
}
private void verifyAuditEvent( LoggingEvent event, String resourceName,
String resourceType, String action, String outcome, String targetService,
String message ) {
event.getMDCCopy();
CorrelationContext cc = (CorrelationContext) event.getMDC( Log4jCorrelationService.MDC_CORRELATION_CONTEXT_KEY );
assertThat( cc, notNullValue() );
assertThat( cc.getRequestId(), is( notNullValue() ) );
AuditContext ac = (AuditContext) event.getMDC( Log4jAuditService.MDC_AUDIT_CONTEXT_KEY );
assertThat( ac, notNullValue() );
assertThat( ac.getRemoteIp(), is( ADDRESS ) );
assertThat( ac.getRemoteHostname(), is( HOST ) );
assertThat( (String) event.getMDC( AuditConstants.MDC_SERVICE_KEY ), is( AuditConstants.KNOX_SERVICE_NAME ) );
assertThat( (String) event.getMDC( AuditConstants.MDC_COMPONENT_KEY ), is( AuditConstants.KNOX_COMPONENT_NAME ) );
assertThat( (String) event.getLoggerName(), is( AuditConstants.DEFAULT_AUDITOR_NAME ) );
verifyValue( (String) event.getMDC( AuditConstants.MDC_RESOURCE_NAME_KEY ), resourceName );
verifyValue( (String) event.getMDC( AuditConstants.MDC_RESOURCE_TYPE_KEY ), resourceType );
verifyValue( (String) event.getMDC( AuditConstants.MDC_ACTION_KEY ), action );
verifyValue( (String) event.getMDC( AuditConstants.MDC_OUTCOME_KEY ), outcome );
verifyValue( ac.getTargetServiceName(), targetService );
verifyValue( event.getRenderedMessage(), message );
}
private void verifyValue( String actual, String expected ) {
if( expected == null ) {
assertThat( actual, nullValue() );
} else {
assertThat( actual, is( expected ) );
}
}
private String getRequestId( LoggingEvent event ) {
CorrelationContext cc = (CorrelationContext) event
.getMDC( Log4jCorrelationService.MDC_CORRELATION_CONTEXT_KEY );
return cc == null ? null : cc.getRequestId();
}
}