Commit a5e1e98a authored by Guus der Kinderen's avatar Guus der Kinderen

OF-946: Admin Panel

This commit adds ConnectionListener and -Configuration for the webadmin panel.
This required some properties to be optional (where they were not).
parent e58d590b
......@@ -142,34 +142,43 @@ public class AdminConsolePlugin implements Plugin {
sslEnabled = false;
try {
final IdentityStore identityStore = CertificateStoreManager.getIdentityStore( ConnectionType.WEBADMIN );
if (adminSecurePort > 0 && !identityStore.getAllCertificates().isEmpty() )
if (adminSecurePort > 0 )
{
if ( !identityStore.containsDomainCertificate( "RSA" )) {
Log.warn("Admin console: Using RSA certificates but they are not valid for the hosted domain");
if ( identityStore.getAllCertificates().isEmpty() )
{
Log.warn( "Admin console: Identity store does not have any certificates. HTTPS will be unavailable." );
}
else
{
if ( !identityStore.containsDomainCertificate( "RSA" ) )
{
Log.warn( "Admin console: Using RSA certificates but they are not valid for the hosted domain" );
}
final ConnectionManagerImpl connectionManager = ((ConnectionManagerImpl) XMPPServer.getInstance().getConnectionManager());
final ConnectionManagerImpl connectionManager = ( (ConnectionManagerImpl) XMPPServer.getInstance().getConnectionManager() );
final ConnectionConfiguration configuration = connectionManager.getConfiguration( ConnectionType.WEBADMIN, true );
final SslContextFactory sslContextFactory = configuration.getSslContextFactory();
final ServerConnector httpsConnector;
if ("npn".equals(JiveGlobals.getXMLProperty("spdy.protocol", ""))) {
httpsConnector = new HTTPSPDYServerConnector(adminServer, sslContextFactory);
} else {
HttpConfiguration httpsConfig = new HttpConfiguration();
if ( "npn".equals( JiveGlobals.getXMLProperty( "spdy.protocol", "" ) ) )
{
httpsConnector = new HTTPSPDYServerConnector( adminServer, sslContextFactory );
}
else
{
final HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.setSendServerVersion( false );
httpsConfig.setSecureScheme("https");
httpsConfig.setSecurePort(adminSecurePort);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
httpsConfig.setSecureScheme( "https" );
httpsConfig.setSecurePort( adminSecurePort );
httpsConfig.addCustomizer( new SecureRequestCustomizer() );
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpsConfig);
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, org.eclipse.jetty.http.HttpVersion.HTTP_1_1.toString());
final HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory( httpsConfig );
final SslConnectionFactory sslConnectionFactory = new SslConnectionFactory( sslContextFactory, org.eclipse.jetty.http.HttpVersion.HTTP_1_1.toString() );
httpsConnector = new ServerConnector(adminServer, null, null, null, -1, serverThreads,
sslConnectionFactory, httpConnectionFactory);
httpsConnector = new ServerConnector( adminServer, null, null, null, -1, serverThreads,
sslConnectionFactory, httpConnectionFactory );
}
String bindInterface = getBindInterface();
final String bindInterface = getBindInterface();
httpsConnector.setHost(bindInterface);
httpsConnector.setPort(adminSecurePort);
adminServer.addConnector(httpsConnector);
......@@ -177,8 +186,10 @@ public class AdminConsolePlugin implements Plugin {
sslEnabled = true;
}
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
catch ( Exception e )
{
Log.error( "An exception occured while trying to make available the admin console via HTTPS.", e );
}
// Make sure that at least one connector was registered.
......
......@@ -4,13 +4,13 @@ import org.apache.mina.filter.ssl.SslFilter;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.keystore.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.net.InetAddress;
import java.security.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.*;
/**
* Configuration for a socket connection.
......@@ -21,6 +21,7 @@ import java.util.Set;
*/
public class ConnectionConfiguration
{
private final Logger Log;
private final ConnectionType type;
private final int maxThreadPoolSize;
private final int maxBufferSize;
......@@ -209,6 +210,7 @@ public class ConnectionConfiguration
return sslContextFactory;
}
Log.info( "Creating new SslContextFactory instance" );
try
{
sslContextFactory = new SslContextFactory();
......@@ -219,10 +221,18 @@ public class ConnectionConfiguration
sslContextFactory.setKeyStore( identityStore.getStore() );
sslContextFactory.setKeyStorePassword( new String( identityStoreConfiguration.getPassword() ) );
// Configure protocol and cipher suite support.
// Configure protocol support
if ( getEncryptionProtocolsEnabled() != null && !getEncryptionProtocolsEnabled().isEmpty() )
{
sslContextFactory.setIncludeProtocols( getEncryptionProtocolsEnabled().toArray( new String[ getEncryptionProtocolsEnabled().size() ] ) );
}
sslContextFactory.setExcludeProtocols( getEncryptionProtocolsDisabled().toArray( new String[ getEncryptionProtocolsDisabled().size() ] ) );
// Configure cipher suite support.
if ( getCipherSuitesEnabled() != null && !getCipherSuitesEnabled().isEmpty() )
{
sslContextFactory.setIncludeCipherSuites( getCipherSuitesEnabled().toArray( new String[ getCipherSuitesEnabled().size() ] ) );
}
sslContextFactory.setExcludeCipherSuites( getCipherSuitesDisabled().toArray( new String[ getCipherSuitesDisabled().size() ] ) );
//Set policy for checking client certificates
......@@ -347,14 +357,25 @@ public class ConnectionConfiguration
this.trustStoreConfiguration = trustStoreConfiguration;
this.acceptSelfSignedCertificates = acceptSelfSignedCertificates;
this.verifyCertificateValidity = verifyCertificateValidity;
this.encryptionProtocolsEnabled = Collections.unmodifiableSet( encryptionProtocolsEnabled );
// Remove all disabled protocols from the enabled ones.
final Set<String> protocolsEnabled = new HashSet<>();
protocolsEnabled.addAll( encryptionProtocolsEnabled );
protocolsEnabled.removeAll( encryptionProtocolsDisabled );
this.encryptionProtocolsEnabled = Collections.unmodifiableSet( protocolsEnabled );
this.encryptionProtocolsDisabled = Collections.unmodifiableSet( encryptionProtocolsDisabled );
this.cipherSuitesEnabled = Collections.unmodifiableSet( cipherSuitesEnabled );
// Remove all disabled suites from the enabled ones.
final Set<String> suitesEnabled = new HashSet<>();
suitesEnabled.addAll( cipherSuitesEnabled );
suitesEnabled.removeAll( cipherSuitesDisabled );
this.cipherSuitesEnabled = Collections.unmodifiableSet( suitesEnabled );
this.cipherSuitesDisabled = Collections.unmodifiableSet( cipherSuitesDisabled );
this.identityStore = CertificateStoreManager.getIdentityStore( type );
this.trustStore = CertificateStoreManager.getTrustStore( type );
this.Log = LoggerFactory.getLogger( this.getClass().getName() + "["+port+"-"+type+"]" );
}
public Connection.TLSPolicy getTlsPolicy()
......
......@@ -2,6 +2,7 @@ package org.jivesoftware.openfire.spi;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.ConnectionManager;
import org.jivesoftware.openfire.ServerPort;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.keystore.CertificateStore;
......@@ -40,10 +41,37 @@ public class ConnectionListener
// Name of properties used to configure the acceptor.
private final String tcpPortPropertyName;
/**
* Name of property that toggles availability. 'null' indicates that the listener should always be enabled (and
* cannot be turned on/off).
*/
private final String isEnabledPropertyName;
/**
* Name of property that configures the maximum threads that can currently be processing IO related to this
* listener. 'null' indicates that a default number should be used.
*/
private final String maxPoolSizePropertyName; // Max threads
/**
* Name of property that configures the maximum amount (in bytes) of IO data can be cached, pending processing.
* 'null' indicates that the cache size is unbounded. Unbounded caches should be used for high-volume and/or trusted
* connections only (if at all).
*/
private final String maxReadBufferPropertyName; // Max buffer size
/**
* Name of property that configures the TLS policy that's applicable to this listener. Instead of a property name,
* the name of a {@link org.jivesoftware.openfire.Connection.TLSPolicy} can be used to indicate that this listener
* is 'hard-coded' in this state (configuration changes will cause exceptions to be thrown).
*/
private final String tlsPolicyPropertyName;
/**
* Name of property that configures the policy regarding mutual authentication that's applicable to this listener.
* 'null' indicates that this policy cannot be configured and 'disabled' should be used as a default.
*/
private final String clientAuthPolicyPropertyName;
// The entity that performs the acceptance of new (socket) connections.
......@@ -64,6 +92,12 @@ public class ConnectionListener
/**
* Instantiates a new connection listener.
*
* @param isEnabledPropertyName Property name (of a boolean) that toggles availability. Null to indicate that this listener is 'always on'
* @param maxPoolSizePropertyName Property name (of an int) that defines maximum IO processing threads. Null causes an unconfigurable default amount to be used.
* @param maxReadBufferPropertyName Property name (of an int) that defines maximum amount (in bytes) of IO data can be cached, pending processing. Null to indicate boundless caches.
* @param tlsPolicyPropertyName Property name (of a string) that defines the applicable TLS Policy. Or, the value {@link org.jivesoftware.openfire.Connection.TLSPolicy} to indicate unconfigurable TLS Policy. Cannot be null.
* @param clientAuthPolicyPropertyName Property name (of an string) that defines maximum IO processing threads. Null causes a unconfigurabel value of 'wanted' to be used.
*/
public ConnectionListener( ConnectionType type, String tcpPortPropertyName, int defaultPort, String isEnabledPropertyName, String maxPoolSizePropertyName, String maxReadBufferPropertyName, String tlsPolicyPropertyName, String clientAuthPolicyPropertyName, InetAddress bindAddress, CertificateStoreConfiguration identityStoreConfiguration, CertificateStoreConfiguration trustStoreConfiguration )
{
......@@ -92,6 +126,11 @@ public class ConnectionListener
*/
public boolean isEnabled()
{
// Not providing a property name indicates that availability cannot be toggled. The listener is 'always on'.
if (isEnabledPropertyName == null )
{
return true;
}
// TODO if this is an SSL connection, legacy code required the existence of at least one certificate in the identity store in addition to the property value (although no such requirement is enforced for a TLS connection that might or might not be elevated to encrypted).
return JiveGlobals.getBooleanProperty( isEnabledPropertyName, true );
}
......@@ -102,6 +141,12 @@ public class ConnectionListener
*/
public synchronized void enable( boolean enable )
{
// Not providing a property name indicates that availability cannot be toggled. The listener is 'always on'.
if ( isEnabledPropertyName == null && !enable )
{
throw new IllegalArgumentException( "This listener cannot be disabled!" );
}
final boolean isRunning = connectionAcceptor != null;
if ( enable == isRunning )
{
......@@ -137,6 +182,19 @@ public class ConnectionListener
*/
public synchronized void start()
{
// TODO Start all connection types here, by supplying more connection acceptors other than a MINA-based one.
switch ( getType() )
{
case SOCKET_S2S:
case BOSH_C2S:
case ADMIN:
case WEBADMIN:
Log.debug( "Not starting a (NIO-based) connection acceptor, as connections of type " + getType() + " depend on another IO technology.");
return;
default:
}
if ( !isEnabled() )
{
Log.debug( "Not starting: disabled by configuration." );
......@@ -171,7 +229,16 @@ public class ConnectionListener
*/
public ConnectionConfiguration generateConnectionConfiguration()
{
final int maxThreadPoolSize = JiveGlobals.getIntProperty( maxPoolSizePropertyName, 16 );
final int defaultMaxPoolSize = 16;
final int maxThreadPoolSize;
if ( maxPoolSizePropertyName == null )
{
maxThreadPoolSize = defaultMaxPoolSize;
}
else
{
maxThreadPoolSize = JiveGlobals.getIntProperty( maxPoolSizePropertyName, defaultMaxPoolSize );
}
final int maxBufferSize;
if ( maxReadBufferPropertyName != null )
......@@ -180,19 +247,19 @@ public class ConnectionListener
}
else
{
maxBufferSize = -1; // No upper bound. Should be used for high-volume & trusted connections only (if at all).
maxBufferSize = -1; // No upper bound.
}
Connection.ClientAuth clientAuth;
if ( clientAuthPolicyPropertyName == null )
{
clientAuth = Connection.ClientAuth.wanted;
clientAuth = Connection.ClientAuth.disabled;
}
else
{
try
{
final String value = JiveGlobals.getProperty( clientAuthPolicyPropertyName, Connection.ClientAuth.wanted.name() );
final String value = JiveGlobals.getProperty( clientAuthPolicyPropertyName, Connection.ClientAuth.disabled.name() );
clientAuth = Connection.ClientAuth.valueOf( value );
}
catch ( IllegalArgumentException e )
......@@ -357,6 +424,21 @@ public class ConnectionListener
restart();
}
/**
* Returns the applicable TLS policy, but only when it is hardcoded (and inconfigurable).
* @return a policy or null.
*/
private Connection.TLSPolicy getHardcodedTLSPolicy()
{
try
{
return Connection.TLSPolicy.valueOf( tlsPolicyPropertyName );
} catch ( IllegalArgumentException ex ) {
// Not hardcoded!
return null;
}
}
/**
* Returns whether TLS is mandatory, optional, disabled or mandatory immediately for new connections. When TLS is
* mandatory connections are required to be encrypted or otherwise will be closed.
......@@ -368,11 +450,14 @@ public class ConnectionListener
*/
public Connection.TLSPolicy getTLSPolicy()
{
if ( tlsPolicyPropertyName.equals( Connection.TLSPolicy.legacyMode.name() ) )
final Connection.TLSPolicy hardcoded = getHardcodedTLSPolicy();
if ( hardcoded != null )
{
return Connection.TLSPolicy.legacyMode;
return hardcoded;
}
else
{
final String policyName = JiveGlobals.getProperty( tlsPolicyPropertyName, Connection.TLSPolicy.optional.toString() );
Connection.TLSPolicy tlsPolicy;
try
......@@ -386,6 +471,7 @@ public class ConnectionListener
}
return tlsPolicy;
}
}
/**
* Sets whether TLS is mandatory, optional, disabled or mandatory immediately for new connections. When TLS is
......@@ -414,6 +500,12 @@ public class ConnectionListener
return;
}
final Connection.TLSPolicy hardcoded = getHardcodedTLSPolicy();
if ( hardcoded != null )
{
throw new IllegalArgumentException( "The TLS Policy for this listener is hardcoded (to '"+hardcoded+"'). It cannot be changed." );
}
if ( Connection.TLSPolicy.legacyMode.equals( policy ) )
{
Log.warn( "Ignoring TLS Policy change request (to '{}'): You cannot reconfigure an existing connection (from '{}') into legacy mode!", policy, oldPolicy );
......
......@@ -63,6 +63,8 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
private final ConnectionListener componentSslListener;
private final ConnectionListener connectionManagerListener; // Also known as 'multiplexer'
private final ConnectionListener connectionManagerSslListener; // Also known as 'multiplexer'
private final ConnectionListener webAdminListener;
private final ConnectionListener webAdminSslListener;
/**
* Instantiates a new connection manager.
......@@ -177,6 +179,34 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
CertificateStoreManager.getIdentityStoreConfiguration( ConnectionType.CONNECTION_MANAGER ),
CertificateStoreManager.getTrustStoreConfiguration( ConnectionType.CONNECTION_MANAGER )
);
// Admin console (the Openfire web-admin) // TODO these use the XML properties instead of normal properties!
webAdminListener = new ConnectionListener(
ConnectionType.WEBADMIN,
"adminConsole.port",
9090,
null,
"adminConsole.serverThreads",
null,
Connection.TLSPolicy.disabled.name(), // StartTLS over HTTP? Should use webAdminSslListener instead.
null,
bindAddress,
CertificateStoreManager.getIdentityStoreConfiguration( ConnectionType.WEBADMIN ),
CertificateStoreManager.getTrustStoreConfiguration( ConnectionType.WEBADMIN )
);
webAdminSslListener = new ConnectionListener(
ConnectionType.WEBADMIN,
"adminConsole.securePort",
9091,
null,
"adminConsole.serverThreads",
null,
Connection.TLSPolicy.legacyMode.name(),
null,
bindAddress,
CertificateStoreManager.getIdentityStoreConfiguration( ConnectionType.WEBADMIN ),
CertificateStoreManager.getTrustStoreConfiguration( ConnectionType.WEBADMIN )
);
}
/**
......@@ -283,6 +313,8 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
listeners.add( componentSslListener );
listeners.add( connectionManagerListener );
listeners.add( connectionManagerSslListener );
listeners.add( webAdminListener );
listeners.add( webAdminSslListener );
return listeners;
}
......@@ -326,6 +358,12 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
return connectionManagerListener;
}
case WEBADMIN:
if (startInSslMode) {
return webAdminSslListener;
} else {
return webAdminListener;
}
default:
throw new IllegalStateException( "Unknown connection type: "+ type );
}
......
......@@ -155,6 +155,7 @@
<tr>
<th>&nbsp;</th>
<th nowrap><fmt:message key="component.session.label.domain" /></th>
<th>&nbsp;</th>
<th nowrap><fmt:message key="component.session.label.name" /></th>
<th nowrap><fmt:message key="component.session.label.category" /></th>
<th nowrap><fmt:message key="component.session.label.type" /></th>
......@@ -187,13 +188,24 @@
<td width="43%" nowrap>
<a href="component-session-details.jsp?jid=<%= URLEncoder.encode(componentSession.getAddress().toString(), "UTF-8") %>" title="<fmt:message key="session.row.cliked" />"><%= componentSession.getAddress() %></a>
</td>
<td align="center" width="15%" nowrap>
<td width="1%">
<% if (componentSession.isSecure()) {
if (componentSession.getPeerCertificates() != null && componentSession.getPeerCertificates().length > 0) { %>
<img src="images/lock_both.gif" width="16" height="16" border="0" title="<fmt:message key='session.row.cliked_ssl' /> (mutual authentication)" alt="<fmt:message key='session.row.cliked_ssl' /> (mutual authentication)">
<% } else { %>
<img src="images/lock.gif" width="16" height="16" border="0" title="<fmt:message key='session.row.cliked_ssl' />" alt="<fmt:message key='session.row.cliked_ssl' />">
<% }
} else { %>
<img src="images/blank.gif" width="1" height="1" alt="">
<% } %>
</td>
<td width="15%" nowrap>
<%= StringUtils.escapeHTMLTags(componentSession.getExternalComponent().getName()) %>
</td>
<td align="center" width="10%" nowrap>
<td width="10%" nowrap>
<%= StringUtils.escapeHTMLTags(componentSession.getExternalComponent().getCategory()) %>
</td>
<td align="center" width="10%" nowrap>
<td width="10%" nowrap>
<table border="0">
<tr valign="center">
<% if ("gateway".equals(componentSession.getExternalComponent().getCategory())) {
......@@ -235,10 +247,10 @@
boolean sameCreationDay = nowCal.get(Calendar.DAY_OF_YEAR) == creationCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == creationCal.get(Calendar.YEAR);
boolean sameActiveDay = nowCal.get(Calendar.DAY_OF_YEAR) == lastActiveCal.get(Calendar.DAY_OF_YEAR) && nowCal.get(Calendar.YEAR) == lastActiveCal.get(Calendar.YEAR);
%>
<td align="center" width="10%" nowrap>
<td width="9%" nowrap>
<%= sameCreationDay ? JiveGlobals.formatTime(creationDate) : JiveGlobals.formatDateTime(creationDate) %>
</td>
<td align="center" width="10%" nowrap>
<td width="9%" nowrap>
<%= sameActiveDay ? JiveGlobals.formatTime(lastActiveDate) : JiveGlobals.formatDateTime(lastActiveDate) %>
</td>
......
......@@ -480,6 +480,9 @@
case CONNECTION_MANAGER:
typeName = LocaleUtils.getLocalizedString("ports.connection_manager");
break;
case WEBADMIN:
typeName = LocaleUtils.getLocalizedString("ports.admin_console");
break;
default:
typeName = "(unspecified)";
break;
......@@ -492,8 +495,7 @@
final String description;
switch ( connectionListener.getType() ) {
case SOCKET_C2S:
if ( connectionListener.getTLSPolicy().equals( Connection.TLSPolicy.legacyMode ) )
{
if ( connectionListener.getTLSPolicy().equals( Connection.TLSPolicy.legacyMode ) ) {
description = LocaleUtils.getLocalizedString( "ports.client_to_server.desc_old_ssl", Arrays.asList( "<a href='ssl-settings.jsp'>", "</a>" ) );
} else {
description = LocaleUtils.getLocalizedString( "ports.client_to_server.desc", Arrays.asList( "<a href='ssl-settings.jsp'>", "</a>" ) );
......@@ -508,6 +510,13 @@
case CONNECTION_MANAGER:
description = LocaleUtils.getLocalizedString( "ports.connection_manager.desc", Arrays.asList( "<a href='connection-managers-settings.jsp'>", "</a>" ) );
break;
case WEBADMIN:
if ( connectionListener.getTLSPolicy().equals( Connection.TLSPolicy.legacyMode ) ) {
description = LocaleUtils.getLocalizedString( "ports.admin_console.desc_secured" );
} else {
description = LocaleUtils.getLocalizedString( "ports.admin_console.desc_unsecured" );
}
break;
default:
description = "";
break;
......@@ -526,24 +535,6 @@
interfaceName = connectionManager.getListenAddress().getHostName();
}
%>
<tr>
<td><%= adminConsolePlugin.getBindInterface() == null ? LocaleUtils.getLocalizedString("ports.all_ports") : adminConsolePlugin.getBindInterface() %></td>
<td><%= adminConsolePlugin.getAdminUnsecurePort() %></td>
<td><img src="images/blank.gif" width="1" height="1" alt=""></td>
<td><fmt:message key="ports.admin_console" /></td>
<td><fmt:message key="ports.admin_console.desc_unsecured" /></td>
</tr>
<%
if (adminConsolePlugin.getAdminSecurePort() > 0) {
%>
<tr>
<td><%= adminConsolePlugin.getBindInterface() == null ? LocaleUtils.getLocalizedString("ports.all_ports") : adminConsolePlugin.getBindInterface() %></td>
<td><%= adminConsolePlugin.getAdminSecurePort() %></td>
<td><img src="images/lock.gif" width="16" height="16" border="0" alt="<fmt:message key="ports.secure.alt" />" title="<fmt:message key="ports.secure.alt" />"/></td>
<td><fmt:message key="ports.admin_console" /></td>
<td><fmt:message key="ports.admin_console.desc_secured" /></td>
</tr>
<% } %>
<%
if (fileTransferProxy.isProxyEnabled()) {
%>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment