/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.server.mgmt; import io.undertow.server.ListenerRegistry; import io.undertow.server.handlers.ChannelUpgradeHandler; import java.net.BindException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import javax.net.ssl.SSLContext; import io.undertow.server.handlers.resource.ResourceManager; import org.jboss.as.controller.ControlledProcessStateService; import org.jboss.as.controller.ModelController; import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.domain.http.server.ConsoleMode; import org.jboss.as.domain.http.server.ManagementHttpRequestProcessor; import org.jboss.as.domain.http.server.ManagementHttpServer; import org.jboss.as.domain.management.AuthMechanism; import org.jboss.as.domain.management.SecurityRealm; import org.jboss.as.network.ManagedBinding; import org.jboss.as.network.ManagedBindingRegistry; import org.jboss.as.network.NetworkInterfaceBinding; import org.jboss.as.network.SocketBinding; import org.jboss.as.network.SocketBindingManager; import org.jboss.as.server.logging.ServerLogger; import org.jboss.as.server.mgmt.domain.ExtensibleHttpManagement; import org.jboss.as.server.mgmt.domain.HttpManagement; import org.jboss.msc.inject.Injector; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.service.ValueService; import org.jboss.msc.value.ImmediateValue; import org.jboss.msc.value.InjectedValue; import org.wildfly.security.auth.server.HttpAuthenticationFactory; import org.wildfly.common.Assert; import org.xnio.SslClientAuthMode; import org.xnio.XnioWorker; /** * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> */ public class UndertowHttpManagementService implements Service<HttpManagement> { public static final RuntimeCapability<Void> EXTENSIBLE_HTTP_MANAGEMENT_CAPABILITY = RuntimeCapability.Builder.of("org.wildfly.management.http.extensible", ExtensibleHttpManagement.class).build(); public static final ServiceName SERVICE_NAME = EXTENSIBLE_HTTP_MANAGEMENT_CAPABILITY.getCapabilityServiceName(); public static final String SERVER_NAME = "wildfly-managment"; public static final String HTTP_MANAGEMENT = "http-management"; public static final String HTTPS_MANAGEMENT = "https-management"; public static final ServiceName HTTP_UPGRADE_SERVICE_NAME = ServiceName.JBOSS.append("http-upgrade-registry", HTTP_MANAGEMENT); public static final ServiceName HTTPS_UPGRADE_SERVICE_NAME = ServiceName.JBOSS.append("http-upgrade-registry", HTTPS_MANAGEMENT); public static final String JBOSS_REMOTING = "jboss-remoting"; public static final String MANAGEMENT_ENDPOINT = "management-endpoint"; private final InjectedValue<ListenerRegistry> listenerRegistry = new InjectedValue<>(); private final InjectedValue<ModelController> modelControllerValue = new InjectedValue<ModelController>(); private final InjectedValue<SocketBinding> injectedSocketBindingValue = new InjectedValue<SocketBinding>(); private final InjectedValue<SocketBinding> injectedSecureSocketBindingValue = new InjectedValue<SocketBinding>(); private final InjectedValue<NetworkInterfaceBinding> interfaceBindingValue = new InjectedValue<NetworkInterfaceBinding>(); private final InjectedValue<NetworkInterfaceBinding> secureInterfaceBindingValue = new InjectedValue<NetworkInterfaceBinding>(); private final InjectedValue<SocketBindingManager> injectedSocketBindingManager = new InjectedValue<SocketBindingManager>(); private final InjectedValue<Integer> portValue = new InjectedValue<Integer>(); private final InjectedValue<Integer> securePortValue = new InjectedValue<Integer>(); private final InjectedValue<HttpAuthenticationFactory> httpAuthenticationFactoryValue = new InjectedValue<>(); private final InjectedValue<SecurityRealm> securityRealmValue = new InjectedValue<SecurityRealm>(); private final InjectedValue<SSLContext> sslContextValue = new InjectedValue<>(); private final InjectedValue<ControlledProcessStateService> controlledProcessStateServiceValue = new InjectedValue<ControlledProcessStateService>(); private final InjectedValue<ManagementHttpRequestProcessor> requestProcessorValue = new InjectedValue<>(); private final InjectedValue<Collection<String>> allowedOriginsValue = new InjectedValue<Collection<String>>(); private final InjectedValue<XnioWorker> worker = new InjectedValue<>(); private final InjectedValue<Executor> managementExecutor = new InjectedValue<>(); private final ConsoleMode consoleMode; private final String consoleSlot; private ManagementHttpServer serverManagement; private SocketBindingManager socketBindingManager; private boolean useUnmanagedBindings = false; private ManagedBinding basicManagedBinding; private ManagedBinding secureManagedBinding; private ExtensibleHttpManagement httpManagement = new ExtensibleHttpManagement() { @Override public void addStaticContext(String contextName, ResourceManager resourceManager) { Assert.assertNotNull(serverManagement); serverManagement.addStaticContext(contextName, resourceManager); } @Override public void addManagementGetRemapContext(String contextName, final PathRemapper remapper) { Assert.assertNotNull(serverManagement); serverManagement.addManagementGetRemapContext(contextName, new ManagementHttpServer.PathRemapper() { @Override public String remapPath(String originalPath) { return remapper.remapPath(originalPath); } }); } @Override public void removeContext(String contextName) { Assert.assertNotNull(serverManagement); serverManagement.removeContext(contextName); } public InetSocketAddress getHttpSocketAddress(){ return basicManagedBinding == null ? null : basicManagedBinding.getBindAddress(); } public InetSocketAddress getHttpsSocketAddress() { return secureManagedBinding == null ? null : secureManagedBinding.getBindAddress(); } @Override public int getHttpPort() { if (basicManagedBinding != null) { return basicManagedBinding.getBindAddress().getPort(); } Integer port = portValue.getOptionalValue(); if (port != null) { return port; } return -1; } @Override public NetworkInterfaceBinding getHttpNetworkInterfaceBinding() { NetworkInterfaceBinding binding = interfaceBindingValue.getOptionalValue(); if (binding == null) { SocketBinding socketBinding = injectedSocketBindingValue.getOptionalValue(); if (socketBinding != null) { binding = socketBinding.getNetworkInterfaceBinding(); } } return binding; } @Override public int getHttpsPort() { if (secureManagedBinding != null) { return secureManagedBinding.getBindAddress().getPort(); } Integer securePort = securePortValue.getOptionalValue(); if (securePort != null) { // return securePort; } return -1; } @Override public NetworkInterfaceBinding getHttpsNetworkInterfaceBinding() { NetworkInterfaceBinding binding = interfaceBindingValue.getOptionalValue(); if (binding == null) { SocketBinding socketBinding = injectedSecureSocketBindingValue.getOptionalValue(); if (socketBinding != null) { binding = socketBinding.getNetworkInterfaceBinding(); } } return binding; } @Override public boolean hasConsole() { return consoleMode.hasConsole(); } }; public UndertowHttpManagementService(ConsoleMode consoleMode, String consoleSlot) { this.consoleMode = consoleMode; this.consoleSlot = consoleSlot; } /** * Starts the service. * * @param context The start context * @throws StartException If any errors occur */ @Override public synchronized void start(StartContext context) throws StartException { final ModelController modelController = modelControllerValue.getValue(); final ControlledProcessStateService controlledProcessStateService = controlledProcessStateServiceValue.getValue(); socketBindingManager = injectedSocketBindingManager.getOptionalValue(); final SecurityRealm securityRealm = securityRealmValue.getOptionalValue(); final HttpAuthenticationFactory httpAuthenticationFactory = httpAuthenticationFactoryValue.getOptionalValue(); final SslClientAuthMode sslClientAuthMode; SSLContext sslContext = sslContextValue.getOptionalValue(); if (sslContext == null && securityRealm != null) { sslContext = securityRealm.getSSLContext(); sslClientAuthMode = getSslClientAuthMode(securityRealm); } else { sslClientAuthMode = null; } InetSocketAddress bindAddress = null; InetSocketAddress secureBindAddress = null; final SocketBinding basicBinding = injectedSocketBindingValue.getOptionalValue(); final SocketBinding secureBinding = injectedSecureSocketBindingValue.getOptionalValue(); final NetworkInterfaceBinding interfaceBinding = interfaceBindingValue.getOptionalValue(); final NetworkInterfaceBinding secureInterfaceBinding = secureInterfaceBindingValue.getOptionalValue(); if (interfaceBinding != null) { useUnmanagedBindings = true; final int port = portValue.getOptionalValue(); if (port > 0) { bindAddress = new InetSocketAddress(interfaceBinding.getAddress(), port); } final int securePort = securePortValue.getOptionalValue(); if (securePort > 0) { InetAddress secureAddress = secureInterfaceBinding == null ? interfaceBinding.getAddress() : secureInterfaceBinding.getAddress(); secureBindAddress = new InetSocketAddress(secureAddress, securePort); } } else { if (basicBinding != null) { bindAddress = basicBinding.getSocketAddress(); } if (secureBinding != null) { secureBindAddress = secureBinding.getSocketAddress(); } } List<ListenerRegistry.Listener> listeners = new ArrayList<>(); //TODO: rethink this whole ListenerRegistry business if(bindAddress != null) { ListenerRegistry.Listener http = new ListenerRegistry.Listener("http", HTTP_MANAGEMENT, SERVER_NAME, bindAddress); http.setContextInformation("socket-binding", basicBinding); listeners.add(http); } if(secureBindAddress != null) { ListenerRegistry.Listener https = new ListenerRegistry.Listener("https", HTTPS_MANAGEMENT, SERVER_NAME, bindAddress); https.setContextInformation("socket-binding", secureBinding); listeners.add(https); } final ChannelUpgradeHandler upgradeHandler = new ChannelUpgradeHandler(); context.getChildTarget().addService(HTTP_UPGRADE_SERVICE_NAME, new ValueService<Object>(new ImmediateValue<Object>(upgradeHandler))) .addAliases(HTTPS_UPGRADE_SERVICE_NAME) //just to keep things consistent, should not be used for now .install(); for (ListenerRegistry.Listener listener : listeners) { listener.addHttpUpgradeMetadata(new ListenerRegistry.HttpUpgradeMetadata(JBOSS_REMOTING, MANAGEMENT_ENDPOINT)); } if(listenerRegistry.getOptionalValue() != null) { for(ListenerRegistry.Listener listener : listeners) { listenerRegistry.getOptionalValue().addListener(listener); } } final ManagementHttpRequestProcessor requestProcessor = requestProcessorValue.getValue(); try { serverManagement = ManagementHttpServer.builder() .setBindAddress(bindAddress) .setSecureBindAddress(secureBindAddress) .setModelController(modelController) .setSecurityRealm(securityRealm) .setSSLContext(sslContext) .setSSLClientAuthMode(sslClientAuthMode) .setHttpAuthenticationFactory(httpAuthenticationFactory) .setControlledProcessStateService(controlledProcessStateService) .setConsoleMode(consoleMode) .setConsoleSlot(consoleSlot) .setChannelUpgradeHandler(upgradeHandler) .setManagementHttpRequestProcessor(requestProcessor) .setAllowedOrigins(allowedOriginsValue.getOptionalValue()) .setWorker(worker.getValue()) .setExecutor(managementExecutor.getValue()) .build(); serverManagement.start(); // Register the now-created sockets with the SBM if (socketBindingManager != null) { if (useUnmanagedBindings) { SocketBindingManager.UnnamedBindingRegistry registry = socketBindingManager.getUnnamedRegistry(); if (bindAddress != null) { basicManagedBinding = ManagedBinding.Factory.createSimpleManagedBinding("management-http", bindAddress, null); registry.registerBinding(basicManagedBinding); } if (secureBindAddress != null) { secureManagedBinding = ManagedBinding.Factory.createSimpleManagedBinding("management-https", secureBindAddress, null); registry.registerBinding(secureManagedBinding); } } else { SocketBindingManager.NamedManagedBindingRegistry registry = socketBindingManager.getNamedRegistry(); if (basicBinding != null) { basicManagedBinding = ManagedBinding.Factory.createSimpleManagedBinding(basicBinding); registry.registerBinding(basicManagedBinding); } if (secureBinding != null) { secureManagedBinding = ManagedBinding.Factory.createSimpleManagedBinding(secureBinding); registry.registerBinding(secureManagedBinding); } } } } catch (Exception e) { Throwable cause = e.getCause(); if (e instanceof BindException || cause instanceof BindException) { final StringBuilder sb = new StringBuilder().append(e.getLocalizedMessage()); if (bindAddress != null) sb.append(" ").append(bindAddress); if (secureBindAddress != null) sb.append(" ").append(secureBindAddress); throw new StartException(sb.toString()); } else { throw ServerLogger.ROOT_LOGGER.failedToStartHttpManagementService(e); } } } /** * Stops the service. * * @param context The stop context */ @Override public synchronized void stop(StopContext context) { ListenerRegistry lr = listenerRegistry.getOptionalValue(); if(lr != null) { lr.removeListener(HTTP_MANAGEMENT); lr.removeListener(HTTPS_MANAGEMENT); } if (serverManagement != null) { try { serverManagement.stop(); } finally { serverManagement = null; // Unregister sockets from the SBM if (socketBindingManager != null) { ManagedBindingRegistry registry = useUnmanagedBindings ? socketBindingManager.getUnnamedRegistry() : socketBindingManager.getNamedRegistry(); if (basicManagedBinding != null) { registry.unregisterBinding(basicManagedBinding); basicManagedBinding = null; } if (secureManagedBinding != null) { registry.unregisterBinding(secureManagedBinding); secureManagedBinding = null; } socketBindingManager = null; useUnmanagedBindings = false; } } } } /** * {@inheritDoc} */ @Override public HttpManagement getValue() throws IllegalStateException { return httpManagement; } /** * Get the interface binding injector. * * @return The injector */ public Injector<NetworkInterfaceBinding> getInterfaceInjector() { return interfaceBindingValue; } /** * Get the secure interface binding injector. * * @return The injector */ public Injector<NetworkInterfaceBinding> getSecureInterfaceInjector() { return secureInterfaceBindingValue; } public Injector<SocketBindingManager> getSocketBindingManagerInjector() { return injectedSocketBindingManager; } public Injector<SocketBinding> getSocketBindingInjector() { return injectedSocketBindingValue; } public Injector<SocketBinding> getSecureSocketBindingInjector() { return injectedSecureSocketBindingValue; } /** * Get the management port injector. * * @return The injector */ public Injector<Integer> getPortInjector() { return portValue; } /** * Get the management secure port injector. * * @return The injector */ public Injector<Integer> getSecurePortInjector() { return securePortValue; } /** * Get the model controller injector to dispatch management requests to * * @return the injector */ public Injector<ModelController> getModelControllerInjector() { return modelControllerValue; } /** * Get the security realm injector. * * @return the securityRealmServiceValue */ public InjectedValue<SecurityRealm> getSecurityRealmInjector() { return securityRealmValue; } /** * Get the SSLContext injector. * * @return the SSLContext injector. */ public Injector<SSLContext> getSSLContextInjector() { return sslContextValue; } /** * Get the {@link Injector} for the HTTP authentication factory. * * @return The {@link Injector} for the HTTP authentication factory. */ public Injector<HttpAuthenticationFactory> getHttpAuthenticationFactoryInjector() { return httpAuthenticationFactoryValue; } /** * Get the security realm injector. * * @return the securityRealmServiceValue */ public InjectedValue<ControlledProcessStateService> getControlledProcessStateServiceInjector() { return controlledProcessStateServiceValue; } public InjectedValue<ListenerRegistry> getListenerRegistry() { return listenerRegistry; } public InjectedValue<ManagementHttpRequestProcessor> getRequestProcessorValue() { return requestProcessorValue; } public InjectedValue<Collection<String>> getAllowedOriginsInjector() { return allowedOriginsValue; } public InjectedValue<XnioWorker> getWorker() { return worker; } public InjectedValue<Executor> getManagementExecutor() { return managementExecutor; } private static SslClientAuthMode getSslClientAuthMode(final SecurityRealm securityRealm) { Set<AuthMechanism> supportedMechanisms = securityRealm.getSupportedAuthenticationMechanisms(); if (supportedMechanisms.contains(AuthMechanism.CLIENT_CERT)) { if (supportedMechanisms.contains(AuthMechanism.DIGEST) || supportedMechanisms.contains(AuthMechanism.PLAIN)) { // Username / Password auth is possible so don't mandate a client certificate. return SslClientAuthMode.REQUESTED; } else { return SslClientAuthMode.REQUIRED; } } return null; } }