Wednesday, January 29, 2014

Secure custom configuration file(XML) using WSO2 secure vault.


The WSO2 secure vault is an implementation of synapse secure vault (modified version), it  is used to secure plain text password which is contains in configuration  files of WSO2 products.  Please go through the below link to get more information[1].

[1]http://docs.wso2.org/display/Carbon420/WSO2+Carbon+Secure+Vault

Here I'm going to show you, how can we secure the plain text password which contains in our own configuration file(XML type). This is very useful when we write custom extensions for WSO2 products.

Lets assume that our new custom module has flowing configuration file(myconf-test.xml) and need to secured the plain text password using the WSO2 secure vault.

<myconf>
   <module serverURL="local://services/" remote="false">
       <username>admin</username>
       <password>admin</password>
   </module>
 </myconf>

1). Open the cipher-tool.properties file which is under <product_home>/repository/conf/security and add the following entry.

myconf.module.password=myconf-test.xml//myconf/module/password,true

2). Open the cipher-text.properties which is under <product_home>/repository/conf/security
and add the following entry.

myconf.module.password=[admin]

3). Go to the <product_home>/bin and execute the following command.

sh ciphertool.sh  -Dconfigure

4).  Now you have to provide the answers for the following questions ?

[Please Enter Primary KeyStore Password of Carbon Server : ]

 Answer is : wso2carbon

5). After enter the keystore  password , you should see the success message in the console output.

Protected Token [myconf.module.password] is updated in myconf-test.xml successfully

6). If you look at the myconf-test.xml again, you should see the password element has been modified as bellow.

<password svns:secretAlias="myconf.module.password">password</password>

7). If you look at the cipher-text.properties file again, you should see the following like entry has been update.

myconf.module.password=Tihb7iTeIrcfWXI6i0m+tMw7OUZKFbSSfl+ngNVS8usB0Q2igFZfBAEFvlq4vPJhZc+b59kuVFBl\nwmfVEWHUDmw3+Nz1JJJl1yds8RA5MV+YrMTuQpl0az2/suW7qJnLvO/fMDTPICQBF8TMxdr3KG5G\nsBym+1MW4B3zuwEtR5g\=

7). Following java class  used to read the configuration file from the distribution and resolved the
     secured password.

    (  Note: If you open from IDE , please point the <product_home>/repository/components/plugins
       to class path.)

ModuleConfig.java


/*
 * Copyright (c) 2006, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed 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.sample.securevault;


import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.securevault.SecretResolver;
import org.wso2.securevault.SecretResolverFactory;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


// Following sample contains the password element, that is one going to be secure using WSO2 secure vault.

/**
 * 
 * 
 * admin
 * admin
 * 
 * 
 */
public class ModuleConf {

    private static final Log log = LogFactory.getLog(ModuleConf.class);

    private String username;
    private String password;
    private String serverURL;
    private String remote;

    public ModuleConf() {
        FileInputStream fileInputStream = null;
        //Assumed that configuration file is under the /repository/conf
        String configPath = CarbonUtils.getCarbonHome() + File.separator + "repository" + File.separator + "conf" +
                File.separator + "myconf-test.xml";

        File registryXML = new File(configPath);
        if (registryXML.exists()) {
            try {
                fileInputStream = new FileInputStream(registryXML);

                StAXOMBuilder builder = new StAXOMBuilder(fileInputStream);
                OMElement configElement = builder.getDocumentElement();
                //Initialize the SecretResolver providing the configuration element.
                SecretResolver secretResolver = SecretResolverFactory.create(configElement, false);
                OMElement module = configElement.getFirstChildWithName(new QName("module"));

                if (module != null) {
                    username = module.getFirstChildWithName(new QName("username")).getText();

                    //same entry used in cipher-text.properties and cipher-tool.properties.
                    String secretAlias = "myconf.module.password";

                    //Resolved the secret password.
                    if (secretResolver != null && secretResolver.isInitialized()) {
                        if (secretResolver.isTokenProtected(secretAlias)) {
                            password = secretResolver.resolve(secretAlias);
                        } else {
                            password = module.getFirstChildWithName(new QName("password")).getText();
                        }
                    }
                    serverURL = module.getAttributeValue(new QName("serverURL"));
                    remote = module.getAttributeValue(new QName("remote"));
                }
            } catch (XMLStreamException e) {
                log.error("Unable to parse myconf-test.xml", e);
            } catch (IOException e) {
                log.error("Unable to read myconf-test.xml", e);
            } finally {
                if (fileInputStream != null) {
                    try {
                        fileInputStream.close();
                    } catch (IOException e) {
                        log.error("Failed to close the FileInputStream, file : " + configPath);
                    }
                }
            }
        }
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public String getServerURL() {
        return serverURL;
    }

    public boolean isRemote() {
        return Boolean.valueOf(remote);
    }
}


8). You can checkout complete sample project(SecureVaultSample) from the following SVN location.

 [i]. After that open the build.xml and change the value of the "product.home" property to your  product home directory.

Execute the ant command inside the project home to build the jar file.

[i]https://svn.wso2.org/repos/wso2/people/ajith/blog/SecureVaultSample/

9). Put the jar file which is under target to <product_home>/repository/components/lib.

10). Start the server . (sh wso2server.sh)

11).  When starting the server, it will prompt the following command input to enter the key store password. The answer is "wso2carbon". Then  server will start successfully.


[Enter KeyStore and Private Key Password :]

12). If you planing to  start the server as  background process , then you need to do the following steps before start the server.

i. Create a file named "password-tmp.txt" in <PRODUCT_HOME>/ directory. Add "wso2carbon" (the primary keystore password) to this file and save. By default, the password provider assumes that both private key and keystore passwords are the same. If not, the private key password must be entered in the second line of the file.

ii. Keystore password will be picked up from the "password-tmp.txt" file. Once the server starts, this file is automatically deleted from the file system. Make sure to add this temporary file back whenever you start the sever as a background process. If you name of the password file "password-persist.txt" instead of "password-tmp.txt", then the file will not be deleted after the server starts. Therefore, it will not be required to provide the password in subsequent startups.

13. Finally, if you initialize the ModuleConf class in your custom extension code (new ModuleConf() ) you should be able to get the resolved password from that Object.

Note:

The SecretCallbackHandler is defined in the secret-conf.properties file inside the <product_home>/repository/conf/security.

 The default implementation is set to org.wso2.carbon.securevault.DefaultSecretCallbackHandler.
 You can find the source in the SVN location[2]

[2]https://svn.wso2.org/repos/wso2/carbon/kernel/branches/4.2.0/core/org.wso2.carbon.securevault/4.2.0/src/main/java/org/wso2/carbon/securevault/DefaultSecretCallbackHandler.java

If you want , you can write your own implementation as well, for that you should write your own implementation extending the AbstractSecretCallbackHandler.

Eg: MyConfSecretCallbackHandler.java 


* Copyright (c) 2006, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed 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.sample.securevault;

import org.wso2.securevault.secret.AbstractSecretCallbackHandler;
import org.wso2.securevault.secret.SingleSecretCallback;


public class MyConfSecretCallbackHandler extends AbstractSecretCallbackHandler {
    @Override
    protected void handleSingleSecretCallback(SingleSecretCallback singleSecretCallback) {
        singleSecretCallback.setSecret("wso2carbon");
    }
}

Build the jar which includes  your own SecretCallbackHandler and add the fully qualified class name in secret-conf.properties instead of org.wso2.carbon.securevault.DefaultSecretCallbackHandler.


The summary of the process showing in following diagram.