org.rhq.jndi.AccessCheckingInitialContextFactoryBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.rhq.jndi.AccessCheckingInitialContextFactoryBuilder.java

Source

/*
 * RHQ Management Platform
 * Copyright (C) 2005-2011 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.rhq.jndi;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.event.EventContext;
import javax.naming.event.EventDirContext;
import javax.naming.ldap.LdapContext;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.InitialContextFactoryBuilder;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jnp.interfaces.NamingContextFactory;

import org.rhq.jndi.context.AccessCheckingContextDecorator;
import org.rhq.jndi.context.AccessCheckingContextDecoratorSetContext;
import org.rhq.jndi.context.ContextDecorator;
import org.rhq.jndi.context.URLPreferringContextDecoratorSetContext;
import org.rhq.jndi.util.DecoratorPicker;

/**
 * This initial context factory builder is installed early on during the RHQ server startup
 * and is later on used for obtaining the {@link Context}s for all JNDI lookups in the
 * RHQ server.
 * <p>
 * We use a custom initial context factory builder to prevent the potential malicious 3rd party
 * code (like CLI alert scripts) from supplying custom environment variables to {@link InitialContext} 
 * that would modify the JNDI lookup to skip our security access checks.
 * <p>
 * By using a builder we effectively take control of the initial context creation process
 * and are free to ignore whatever the script is trying to supply.
 * <p>
 * This builder makes sure to install the RHQ server's security access checks to whatever
 * initial context that is configured by the standard environment variables 
 * ({@link Context#INITIAL_CONTEXT_FACTORY}, etc.)
 * <p>
 * This class is heavily inspired by the implementation of a similar builder in JBoss AS 7.
 * 
 * @see AllowRhqServerInternalsAccessPermission
 * 
 * @author Lukas Krejci
 */
public class AccessCheckingInitialContextFactoryBuilder implements InitialContextFactoryBuilder {
    private static final Log LOG = LogFactory.getLog(AccessCheckingInitialContextFactoryBuilder.class);

    /**
     * The list of JNDI name schemes that should be checked for security permissions
     * (in addition to the names with no scheme).
     * 
     * @see AccessCheckingContextDecorator
     */
    private static final String[] CHECKED_SCHEMES = { "java" };

    private static final Set<Class<? extends Context>> SUPPORTED_CONTEXT_INTERFACES;

    static {
        SUPPORTED_CONTEXT_INTERFACES = new HashSet<Class<? extends Context>>();
        SUPPORTED_CONTEXT_INTERFACES.add(Context.class);
        SUPPORTED_CONTEXT_INTERFACES.add(DirContext.class);
        SUPPORTED_CONTEXT_INTERFACES.add(EventContext.class);
        SUPPORTED_CONTEXT_INTERFACES.add(EventDirContext.class);
        SUPPORTED_CONTEXT_INTERFACES.add(LdapContext.class);
    }

    private static final Set<InetAddress> SERVER_BIND_IPS;
    static {
        SERVER_BIND_IPS = new HashSet<InetAddress>();

        try {
            String bindingAddressString = System.getProperty("jboss.bind.address");
            InetAddress bindingAddress = InetAddress.getByName(bindingAddressString);

            if (bindingAddress.isAnyLocalAddress()) {
                Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
                while (ifaces.hasMoreElements()) {
                    NetworkInterface iface = ifaces.nextElement();
                    SERVER_BIND_IPS.addAll(Collections.list(iface.getInetAddresses()));
                }
            } else {
                SERVER_BIND_IPS.add(bindingAddress);
            }
        } catch (SocketException e) {
            LOG.error("Could not obtain the list of local IPs", e);
        } catch (UnknownHostException e) {
            LOG.error("Failed to get the binding address of the RHQ server.", e);
        }
    }

    private static final int JNP_PORT = Integer
            .parseInt(System.getProperty("rhq.server.startup.namingservice.port", "2099"));

    /**
     * This is the default initial context factory that is returned when no other is 
     * configured using the environment variables.
     * <p>
     * It uses {@link NamingContextFactory} as the underlying mechanism - the same
     * as the default configuration in JBoss 4.
     */
    private static final InitialContextFactory DEFAULT_FACTORY = new InitialContextFactory() {
        public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
            return createSecureWrapper(new NamingContextFactory(), environment).getInitialContext(environment);
        }
    };

    /**
     * Create a InitialContext factory.  If the environment does not override the factory class it will use the
     * default context factory.
     * 
     * @param environment The environment
     * @return An initial context factory
     * @throws NamingException If an error occurs loading the factory class.
     */
    public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException {
        final String factoryClassName = (String) environment.get(Context.INITIAL_CONTEXT_FACTORY);
        if (factoryClassName == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("No " + Context.INITIAL_CONTEXT_FACTORY + " set. Using the default factory.");
            }
            return DEFAULT_FACTORY;
        }
        final ClassLoader classLoader = getContextClassLoader();
        try {
            final Class<?> factoryClass = Class.forName(factoryClassName, true, classLoader);
            InitialContextFactory configuredFactory = (InitialContextFactory) factoryClass.newInstance();
            return createSecureWrapper(configuredFactory, environment);
        } catch (Exception e) {
            throw new NamingException("Failed instantiate InitialContextFactory " + factoryClassName
                    + " from classloader " + classLoader);
        }
    }

    private ClassLoader getContextClassLoader() {
        return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
            public ClassLoader run() {
                return Thread.currentThread().getContextClassLoader();
            }
        });
    }

    private static InitialContextFactory createSecureWrapper(InitialContextFactory factory,
            Hashtable<?, ?> environment) {
        String providerUrl = (String) environment.get(Context.PROVIDER_URL);

        if (providerUrl == null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Wrapping " + factory + " of class " + factory.getClass()
                        + " in an access checking wrapper. No provider URL detected.");
            }
            return getAccessCheckingFactory(factory);
        } else {
            try {
                URI uri = new URI(providerUrl);
                InetAddress providerHost = InetAddress.getByName(uri.getHost());

                //check if we are accessing the RHQ server through some remoting
                //interface.
                if (uri.getPort() == JNP_PORT && SERVER_BIND_IPS.contains(providerHost)) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Wrapping " + factory + " of class " + factory.getClass()
                                + " in an access checking wrapper. The provider URL points to this server.");
                    }
                    return getAccessCheckingFactory(factory);
                } else {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Wrapping " + factory + " of class " + factory.getClass()
                                + " in an URL preferring wrapper to enable remote connections.");
                    }
                    return getURLPreferringFactory(factory);
                }
            } catch (URISyntaxException e) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("The " + Context.PROVIDER_URL
                            + " is not a valid URI. Falling back to using the access checking wrapper for the factory "
                            + factory + " of class " + factory.getClass() + ".", e);
                }
                return getAccessCheckingFactory(factory);
            } catch (UnknownHostException e) {
                //let the factory deal with the unknown host...
                //this most probably shouldn't be secured because localhost addresses
                //should be resolvable.
                if (LOG.isDebugEnabled()) {
                    LOG.debug("The " + Context.PROVIDER_URL
                            + " is not resolvable. Falling back to using the URL preferring wrapper for the factory "
                            + factory + " of class " + factory.getClass() + ".", e);
                }
                return getURLPreferringFactory(factory);
            }
        }
    }

    private static InitialContextFactory getAccessCheckingFactory(InitialContextFactory original) {
        ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context, ContextDecorator>>();
        pickers.add(getURLPreferringDecoratorPicker());
        pickers.add(getAccessCheckingDecoratorPicker());

        return new DecoratingInitialContextFactory(original, pickers);
    }

    private static InitialContextFactory getURLPreferringFactory(InitialContextFactory original) {
        ArrayList<DecoratorPicker<Context, ContextDecorator>> pickers = new ArrayList<DecoratorPicker<Context, ContextDecorator>>();
        pickers.add(getURLPreferringDecoratorPicker());

        return new DecoratingInitialContextFactory(original, pickers);
    }

    private static DecoratorPicker<Context, ContextDecorator> getAccessCheckingDecoratorPicker() {
        DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>();
        ret.setContext(new AccessCheckingContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES, CHECKED_SCHEMES));

        return ret;
    }

    private static DecoratorPicker<Context, ContextDecorator> getURLPreferringDecoratorPicker() {
        DecoratorPicker<Context, ContextDecorator> ret = new DecoratorPicker<Context, ContextDecorator>();
        ret.setContext(new URLPreferringContextDecoratorSetContext(SUPPORTED_CONTEXT_INTERFACES));

        return ret;
    }
}