Tuesday, 8 November 2011

EJB3 Web Service Over SSL with JBoss 5.1.0.GA


Scenario:
Create a secure web service that will print a text message on the console.

Solution:

Create the service contract

package za.co.coolparts.services.webservices;

public interface PrinterService {
      public void printMessage(String message);
}

Create the implementation class

package za.co.coolparts.services.webservices;

import javax.annotation.security.RolesAllowed;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.jws.WebService;

import org.jboss.ejb3.annotation.SecurityDomain;
import org.jboss.wsf.spi.annotation.WebContext;

@Stateless
@Remote(PrinterService.class)
@WebService(name="ConsolePrinter",serviceName="ConsolePrinterService",portName="ConsolePrinterPort")
@SecurityDomain("myCustomDomain")
@RolesAllowed({"friend", "employee"})
@WebContext(authMethod="BASIC", transportGuarantee="CONFIDENTIAL", secureWSDLAccess=false)
public class ConsolePrinter implements PrinterService {

  @Override
  public void printMessage(String message) {
    System.out.println("********Printing...***********");
    System.out.println(message);
    System.out.println("********Done Printing.********");  
  }
}


@SecurityDomain: specifies the JAAS application-policy name which will be used by JBoss to authenticate and authorize.

@RolesAllowed: defines which roles are allowed to invoke any of the bean methods.

@WebContext:
authMethod="BASIC" - HTTP Basic authentication will be used.
transportGuarantee="CONFIDENTIAL" - the data is encrypted before being  transmitted.
secureWSDLAccess=false - authentication is not required to access the WSDL.

Create the client and server certificates

Open a terminal and execute the following commands.

Create the server keystore:
keytool -genkey -alias serverkeys -keyalg RSA –keystore server.keystore -storepass passowrd1 -keypass password1

Export the server certificate:
keytool -export -alias serverkeys -keystore server.keystore -storepass password1 -file server.cer

Create the client keystore:
keytool -genkey -alias clientkeys -keyalg RSA -keystore client.keystore -storepass password1 -keypass password1

Export the client certificate:
keytool -export -alias clientkeys -keystore client.keystore -storepass password1 -file client.cer

Import the client certificate in the server.truststore:
keytool -import -v -keystore server.truststore  -storepass password1 -file client.cer

Import the server certificate in the client.truststore:
keytool -import -v -keystore client.truststore  -storepass password1 -file server.cer

Configure JBoss
  • Copy the server.keystore and server.truststore files to the ${jboss.server.home.dir}/conf directory.
  • Edit the ${jboss.server.home.dir}/deploy/jbossweb.sar/server.xml file and add the following connector:
<!-- SSL/TLS Connector configuration using the admin devl guide keystore  -->
      <Connector protocol="HTTP/1.1" SSLEnabled="true"
           port="8443" address="${jboss.bind.address}"
           scheme="https" secure="true" clientAuth="false"
           keystoreFile="${jboss.server.home.dir}/conf/server.keystore"
           keystorePass="password1"
           truststoreFile="${jboss.server.home.dir}/conf/server.truststore"
           truststorePass="password1"
           sslProtocol = "TLS" />

  • Create a file called myCustomDomain-service.xml in the ${jboss.server.home.dir}/deploy folder. This file specifies the domain name and the locations of the keystore and the truststore. It also specifies the location of the authentication config file to use.
<?xml version="1.0" encoding="UTF-8"?>
<server>

    <!-- Configures the myCustomDomain SecurityDomain -->
    <mbean code="org.jboss.security.plugins.JaasSecurityDomain" name="jboss.security:service=SecurityDomain">
         <constructor>
              <arg type="java.lang.String" value="myCustomDomain" />
         </constructor>
         <attribute name="KeyStoreURL">file:${jboss.server.home.dir}/conf/server.keystore</attribute>
         <attribute name="KeyStorePass">password1</attribute>
         <attribute name="TrustStoreURL">file:${jboss.server.home.dir}/conf/server.truststore</attribute>
         <attribute name="TrustStorePass">password1</attribute>
         <depends>jboss.security:service=JaasSecurityManager</depends>
    </mbean>


    <mbean code="org.jboss.security.auth.login.DynamicLoginConfig" name="jboss:service=DynamicLoginConfig">
         <attribute name="AuthConfig">
              file:${jboss.server.home.dir}/conf/myCustomDomain-security-config.xml
         </attribute>
         <depends optional-attribute-name="LoginConfigService">
              jboss.security:service=XMLLoginConfig
         </depends>
         <depends optional-attribute-name="SecurityManagerService">
              jboss.security:service=JaasSecurityManager
         </depends>
    </mbean>
   
</server> 


  • In the ${jboss.server.home.dir}/conf directory create the myCustomDomain-security-config.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<policy>
     <application-policy name="myCustomDomain">
        <authentication>
          <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required">
            <module-option name="securityDomain">java:/jaas/myCustomDomain</module-option>
            <module-option name="usersProperties">props/jbossws-users.properties</module-option>
            <module-option name="rolesProperties">props/jbossws-roles.properties</module-option>
        <module-option name="verifier">za.co.coolparts.CertVerifier</module-option>
     </login-module>
        </authentication>
    </application-policy>
</policy>


The policy uses the org.jboss.security.auth.spi.UsersRolesLoginModule class. For more information on how to configure this module have a read through this article on the JBoss Community Wiki: http://community.jboss.org/wiki/UsersRolesLoginModule.

  • The policy defines a custom verifier, za.co.coolparts.CertVerifier, which allows you to customize the authentication. Create the za.co.coolparts.CertVerifier class as follows for an always trusting implementation.

package za.co.coolparts;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import org.jboss.security.auth.certs.X509CertificateVerifier;

public class CertVerifier implements X509CertificateVerifier {
    public boolean verify(X509Certificate arg0, String arg1, KeyStore arg2, KeyStore arg3) {
        System.out.println("MyCustomCertificateVerifier!!!!!");
        return true;
    }
}

This class needs to be packaged in a separate jar file and deployed in the ${jboss.server.home.dir}/lib folder.

The org.jboss.security.auth.certs.X509CertificateVerifier class is found in the ${jboss.home}/common/lib/jbosssx.jar file.

<dependency>
 <groupId>jboss</groupId>
 <artifactId>jbosssx</artifactId>
 <version>5.1.0.GA</version>
 <scope>system</scope>
 <systemPath>${jboss.home}/common/lib/jbosssx.jar</systemPath>
</dependency>


Consuming the web service

After deployment the wsdl will be available on the following URL:
https://localhost:8443/CoolParts-1.0/ConsolePrinter?wsdl

Run the client with the following command line parameters:
-Djavax.net.ssl.keyStore=ssl/client.keystore
-Djavax.net.ssl.keyStorePassword=password1
-Djavax.net.ssl.trustStore=ssl/client.truststore
-Djavax.net.ssl.trustStorePassword=password1


package za.co.test;
import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
import za.co.coolparts.services.webservices.ConsolePrinter;
import za.co.coolparts.services.webservices.ConsolePrinterBindingStub;
import za.co.coolparts.services.webservices.ConsolePrinterService;
import za.co.coolparts.services.webservices.ConsolePrinterServiceLocator;

public class Client {

 public static void main(String[] args) {

  try {
       ConsolePrinterService service = new ConsolePrinterServiceLocator();
       ConsolePrinter impl = service.getConsolePrinterPort();
       ConsolePrinterBindingStub stub = (ConsolePrinterBindingStub) impl;
       stub.setUsername("kermit");
       stub.setPassword("thefrog");
       stub.printMessage("Hello World!");
                
      } catch (ServiceException e) {
        e.printStackTrace();
      } catch (RemoteException e) {
        e.printStackTrace();
      }
  }
}

Tuesday, 18 October 2011

JBoss startup error java.io.IOException: Access is denied

ERROR [org.jboss.kernel.plugins.dependency.AbstractKernelController] (main) Error installing to Start: name=jboss:service=NamingProviderURLWriter state=Create mode=Manual requiredState=Installed
java.io.IOException: Access is denied


The error indicates that a file that is needed for JBoss to start is being locked for some reason.

Windows indexes files on your hard drive to speed up it's built-in search. The windows indexing service commonly locks the  jnp-service file in the JBOSS_HOME/server/default/data directory. If you don't use Windows search very often, turn off indexing.

To disable the Windows indexing service:

  • Click Start --> Control Panel --> Administrative Tools --> Services
  • Scroll down to "Indexing Service" and double-click it.
  • If the Service status is Running then stop it by clicking the Stop button.
  • To make sure this service does not run again, under Startup type select Disabled.

Thursday, 25 August 2011

Reserved error (-7748); There is no message for this error

This error occurs when trying to link a Sybase table to a MS Access database.

Workaround for this bug:
  • Click Start -> Run -> Type "regedit.exe"
  • Go to the following directory: HKEY_LOCAL_MACHINE -> Software -> ODBC -> ODBC.INI -> your_DSN
  • Right click on your data source and chose "New" -> "String Value" from the menu
  • Rename the new string value to "WorkArounds2" 
  • Double click on "WorkArounds2" in the right hand panel
  • Change "Value data" to 8192

Friday, 20 May 2011

How To Secure The JBoss JMX Console and Web Console

I received an email this morning, from the "Network admin guys" at work, telling me that my dev and production JBoss servers allow "unauthenticated access to an administrative Java servlet".

Here's the exact description of the problem:
The remote web server appears to be a version of JBoss that allows
unauthenticated access to the JMX and/or Web Console servlets used to
manage JBoss and its services. A remote attacker can leverage this
issue to disclose sensitive information about the affected application
or even take control of it.
It turns out that the JBoss community version (AS) allows unauthenticated access to both the jmx-console and the web-console by default.

How to secure the JMX Console

I am using jboss-5.1.0.GA. If you are using a different version then the names of the directories and files may differ.

Step 1: jboss-web.xml
Uncomment the <security-domain>java:/jaas/jmx-console</security-domain> line in the JBOSS_HOME/server/default/deploy/jmx-console.war/WEB-INF/jboss-web.xml file.

Step 2: web.xml
Uncomment the following section in the JBOSS_HOME/server/default/deploy/jmx-console.war/WEB-INF/web.xml file.

<security-constraint>
     <web-resource-collection>
       <web-resource-name>HtmlAdaptor</web-resource-name>
       <description>An example security config that only allows users with the role JBossAdmin to access the HTML JMX console web application
       </description>
       <url-pattern>/*</url-pattern>
       <http-method>GET</http-method>
       <http-method>POST</http-method>
     </web-resource-collection>
     <auth-constraint>
       <role-name>JBossAdmin</role-name>
     </auth-constraint>
</security-constraint>


Step 3: Set the username, password and role
Set the username and password in the JBOSS_HOME/server/default/conf/props/jmx-console-users.properties file. Example: admin=password


Set the role in the JBOSS_HOME/server/default/conf/props/jmx-console-roles.properties file. Example: admin=JBossAdmin,HttpInvoker

Step 4: login-config.xml
Ensure that the JBOSS_HOME/server/default/conf/login-config.xml file is configured to use the correct properties files for the jmx-console.


<!-- A template configuration for the jmx-console web application. This
    defaults to the UsersRolesLoginModule the same as other and should be
    changed to a stronger authentication mechanism as required.
  -->
  <application-policy name="jmx-console">
    <authentication>
      <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule"
        flag="required">
        <module-option name="usersProperties">props/jmx-console-users.properties</module-option>
        <module-option name="rolesProperties">props/jmx-console-roles.properties</module-option>
      </login-module>
    </authentication>
  </application-policy>


Now when you try to access the jmx-console, via http://localhost:8080/jmx-console, you will be prompted to login.



How to secure the Web Console
The steps to secure the web-console are very similar to the steps above. Take note of the following:

For steps 1 and 2:
The jboss-web.xml file and the web.xml file for the web-console can be found in the JBOSS_HOME\server\default\deploy\management\console-mgr.sar\web-console.war\WEB-INF directory.

For step 3:
You can either use the same jmx-console-users.properties and jmx-console-roles.properties files or you can create new web-console-users.properties and web-console-roles.properties files to configure the username, password and role.

For step 4:
Ensure that the JBOSS_HOME/server/default/conf/login-config.xml file is configured to use the correct properties files for the web-console.

The web-console will now prompt the user to login.


For some reason I was unable to login to the web-console without restarting the JBoss server. 

Informatica - FATAL ERROR: caught a fatal signal or exception. Aborting the DTM process due to fatal signal or exception. Signal received SIGSEV(11)

Let me start by saying that I hate Informatica. The only reason I use it is because it's a "company standard".

We have a workflow that simply takes data from one database table and inserts this data into another database table. That's it. No expressions, no lookups, no transformations of any kind. This workflow was not changed during the past year and everything worked fine, until this morning. It failed with the following error:
FATAL ERROR: caught a fatal signal or exception. Aborting the DTM process due to fatal signal or exception. Signal received SIGSEV(11)

There were no other errors or warnings. After looking at the data in the source table and making sure that there were no irregularities and making sure that there were no new constraints added to the target table I realised that this is just one of those tricks that Informatica likes to play on us.

After some "googling" I found a few posts that suggest that the error could be a memory problem. Here's what I did to resolve the issue:

  1. Open the Workflow Manager
  2. Edit the session
  3. Click the Properties tab
  4. Change the "DTM buffer size" (I changed it from Auto to 24000000)
  5. Click the Config Object tab
  6. Change the "Default buffer block size" (I changed it from Auto to 2400000)
  7. Increase the "Line Sequential buffer length"  from 1024 to 2048
  8. Increase the "Maximum Memory Allowed For Auto Memory Attributes" from 512Mb to 1024MB
  9. Increase the "Maximum Percentage of Total Memory Allowed For Auto Memory Attributes" from 5 to 10
  10. Save and run the workflow.

You should of course increase the memory settings according to the amount of data your workflow is processing. These settings just happened to work for me. I couldn't be bothered with any technical explanation of what each of those properties are for. If I had it my way I would have replaced this entire workflow with a simple Java application a long time ago.

MS Excel - Compile error in hidden module

When you start MS Excel you might receive an error similar to the one below:


According to this post Microsoft suggests that this problem may occur when the following conditions are true:
  1. The Microsoft Office Startup folder or the Microsoft Excel Startup folder contains either or both of the following Adobe Acrobat PDFMaker add-in template files:
    • Pdfmaker.dot
    • Pdfmaker.xla
  2. Norton AntiVirus software is installed.
I was helping a colleague troubleshoot this error, with MS Excel 2007, a few days ago. I searched the entire file system and the Pdfmaker files were no where to be found. I even uninstalled Adobe. I uninstalled excel. Re-installed excel. Restarting the PC after each step. I made sure that Norton AntiVirus was not installed. None of this helped. I even installed MS Excel 2003 but we kept getting the same error. I suppose  we could have clicked "OK" and ignored the error if all she wanted to do was create a simple spreadsheet but unfortunately there were some VBA scripts that needed to be run and the error prevented the scripts from running.

Reinstalling MS Office might have fixed the problem but she didn't want to risk losing all her "important settings" in Outlook. As a work around I told her to open Excel in safe mode as this prevents the error from popping up.
When you run excel in safe mode it does not open add-ins or other startup files. It is a clean no-frills mode of operation with many options unavailable. To run Excel in Safe Mode, go to the Windows Start menu, choose Run, and enter excel.exe /s

Tuesday, 17 May 2011

How to poll an email inbox and process all incoming emails using the JavaMail API

I thought I'd share this simple program that can be used to poll an email inbox and process all incoming emails. It uses the JavaMail API.

package za.co.example.emailpoller;
import java.io.IOException;
import java.util.Properties;
import javax.mail.Folder;
import javax.mail.FolderClosedException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.event.MessageCountAdapter;
import javax.mail.event.MessageCountEvent;
import javax.mail.internet.InternetAddress;
import com.sun.mail.imap.IMAPFolder;

public class Poller {

private String host;
private String username;
private String password;
private String protocol;
private String smtpServer;
private int pollingFrequency;
private boolean debug;
private Properties props;

public Poller() throws IOException {
   //This information should be stored in a config file.
   host = "hostname";
   username = "myUsername";
   password = "myPassword";
   protocol = "imap";
   smtpServer = "smtp.myhost.co.za";
   pollingFrequency = 10000;
   debug = false;
   props = new Properties();
   props.put("mail." + protocol + ".auth.plain.disable", "true");
   props.put("mail." + protocol + ".auth.ntlm.disable", "true");
}

public void startPolling(){

   System.out.println("Polling started...");
   final Session session = Session.getInstance(props, null);
   session.setDebug(debug);
   Folder inbox = null;
   Store store = null;

/* This while loop will make sure the connection is reset if the server closes the connection.
* Some MS Exchange Servers may be configured to close the connection if there is no activity on the inbox folder for a time period.
*/
while(true){

try{
System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
System.out.println("New Connection Established");
System.out.println("Host: " + host);
System.out.println("Username: " + username);
System.out.println("Protocol: " + protocol);
System.out.println("Polling Frequency: " + pollingFrequency);
System.out.println("Debug: " + debug);
System.out.println("Smtp Server: " + smtpServer);
System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
System.out.println();

store = session.getStore(protocol);
store.connect(host, username, password);
inbox = store.getFolder("INBOX");

if(inbox == null){
   System.err.println("Cant find INBOX");
   System.exit(1);
}

inbox.open(Folder.READ_ONLY);
inbox.addMessageCountListener(new MessageCountAdapter() {

public void messagesAdded(MessageCountEvent ev) {

Message[] messages = ev.getMessages();
System.out.println("Got " + messages.length + " new messages");

for (int i = 0; i < messages.length; i++) {
try{
System.out.println("***********************************************************");
System.out.println("------------ Message " + (i + 1) + " ------------");
String from = (new InternetAddress(InternetAddress.toString(messages[i].getFrom())).getAddress());
String replyTo = InternetAddress.toString(messages[i].getReplyTo());
String to = InternetAddress.toString(messages[i].getRecipients(Message.RecipientType.TO));
String subject = messages[i].getSubject();
String sentDate = messages[i].getSentDate().toString();
String body = getText(messages[i]);

System.out.println("From: " + from);
System.out.println("Reply To: " + replyTo);
System.out.println("To: " + to);
System.out.println("Subject: " + subject);
System.out.println("Sent Date: " + sentDate);
System.out.println("Body: " + body);
System.out.println("***********************************************************");
}
catch(MessagingException e){
   e.printStackTrace();
} catch (IOException e) {
   e.printStackTrace();
}
}
}
});

// Check mail once in "pollingFrequency" MILLIseconds
boolean supportsIdle = false;
try {
if (inbox instanceof IMAPFolder) {
   IMAPFolder f = (IMAPFolder)inbox;
   f.idle();
   supportsIdle = true;
}
} catch (FolderClosedException fex) {
   fex.printStackTrace();
throw fex;
} catch (MessagingException mex) {
   mex.printStackTrace();
   supportsIdle = false;
}
for (;;) {
if (supportsIdle && inbox instanceof IMAPFolder) {
   IMAPFolder f = (IMAPFolder)inbox;
   f.idle();
   System.out.println("IDLE done");
} else {
   Thread.sleep(pollingFrequency); // sleep for pollingFrequency milliseconds
   // This is to force the IMAP server to send us EXISTS notifications.
   inbox.getMessageCount();
}
}
}catch(Exception e){
   e.printStackTrace();
}
finally{
try {
if(inbox != null){inbox.close(false);}
if(store != null){store.close();}
} catch (MessagingException e) {e.printStackTrace();}
}
}//while
}
/**
* Return the primary text content of the message.
* @param p
* @return
* @throws MessagingException
* @throws IOException
*/
private String getText(Part p) throws MessagingException, IOException {

if (p.isMimeType("text/*")) {
System.out.println("text/*");
String s = (String)p.getContent();
return s;
}

if (p.isMimeType("multipart/alternative")) {
System.out.println("multipart/alternative");

// prefer html text over plain text
Multipart mp = (Multipart)p.getContent();
String text = "";

for (int i = 0; i < mp.getCount(); i++) {
Part bp = mp.getBodyPart(i);
if (bp.isMimeType("text/plain")) {
System.out.println("text/plain");
if (text == null){
text = getText(bp);
}
continue;
} else if (bp.isMimeType("text/html")) {
System.out.println("text/html");
String s = getText(bp);
if (s != null)
return s;
} else {
return getText(bp);
}
}
return text;
}
else if (p.isMimeType("multipart/*")) {
System.out.println("multipart/*");
Multipart mp = (Multipart)p.getContent();
for (int i = 0; i < mp.getCount(); i++) {
String s = getText(mp.getBodyPart(i));
if (s != null)
return s;
}
}

return null;
}

public static void main(String[] args) throws IOException {
   new Poller().startPolling();
}
}