Thursday, May 12, 2011

KSplice Uptrack PHP REST Client

Ever get tired of rebooting your linux server after a kernel update? KSplice has created a tool that allows for kernel updates to occur without having to reboot your server. They provide a web interface at ksplice.com to manage your servers through their GUI, but I had the need to create a PHP client to interact with the KSplice API. Unfortunately the developer documentation is very lack luster, so I am going to give you some help.

I chose to use the Zend framework because it has the Zend_Rest_Client available to build upon. There are really only three different API calls on the KSplice Uptrack REST web service: machines, describe, and authorize.

machines:
returns a JSON list of all the servers registered with KSplice Uptrack
POST /api/1/machines

describe:
returns a JSON of a specific server
POST /api/1/machine/$UUID/describe

authorize:
enables or disables a specific server from using KSplice Uptrack
POST /api/1/machine/$UUID/authorize

First off I create my class and two variables that will be used. One for the Zend_Rest_Client, and the second for the base URI.



/**
* RestClient for the KSplice Uptrack API
*
*/

class Ksplice_Uptrack_RestClient
{

protected $restClient;
private $baseUri;

public function __construct()
{
$baseUri = "https://uptrack.ksplice.com/api/1";
}



The next piece of the puzzle is creating generic POST and GET functions to be used by the actual API calls. These will return PHP Std_Objects after decoding the JSON returned by the KSplice Uptrack API.




private function restPost()
{
$response = $this->restClient->getHttpClient()->request('POST');
$this->errorHandler($response);

return Zend_Json::decode($response->getBody(), Zend_Json::TYPE_OBJECT);
}

private function restGet()
{
$response = $this->restClient->getHttpClient()->request('GET');
$this->errorHandler($response);

return Zend_Json::decode($response->getBody(), Zend_Json::TYPE_OBJECT);
}


I have added an brief error handler.


private function errorHandler($response)
{
if (!($response instanceof Zend_Http_Response) && !($response instanceof Zend_Rest_Client_Result)) {
throw new Exception("Invalid KSplice response received.");
}

switch (get_class($response)) {
case 'Zend_Rest_Client_Result':

break;

case 'Zend_Http_Response':
$restJsonAsObject = Zend_Json::decode($response->getBody(), Zend_Json::TYPE_OBJECT);
if ($response->isError()) {
$responseCode = intval($response->getStatus());
throw new Exception("KSplice Error Received: {$restJsonAsObject->error}");
}

break;

default :
throw new Exception("Cannot process KSplice request at this time.");
break;
}
}


KSplice Uptrack's REST API requires custom headers for authentication and the API calls are appended to the base URI we defined in the constructor. I will add generic methods to set both the request headers and the URI. Additionally, the authorize API call requires raw data in the form of JSON to be sent, so I will add a method to set the raw data of the request.



private function setUri($uri)
{
$this->restClient->getHttpClient()->setUri($uri);
}

private function setHeaders($header, $value)
{
$this->restClient->getHttpClient()->setHeaders($header, $value);
}

private function setRawData($data, $contentType = "application/json")
{
$this->restClient->getHttpClient()->resetParameters();
$this->restClient->getHttpClient()->setRawData($data, $contentType);
}


Now for the final three methods: getMachines, getServer, authorizeServer

getServer and authorizeServer require the UUID to build the URI to the KSplice Uptrack REST service. This is the UUID that was created during the installation of Uptrack on your server. I store it in a database, but it is also found at


public function getMachines()
{
$this->restClient = new Zend_Rest_Client($this->configuration->host);
$this->setUri($this->baseUri.'/machines');
$this->setHeaders('X-Uptrack-User', "MyKspliceUser);
$this->setHeaders('X-Uptrack-Key', "MyKspliceUptrackKey");
return $this->restGet();
}

public function getServer($uuid)
{
$this->restClient = new Zend_Rest_Client($this->configuration->host);
$this->setUri($this->baseUri.'/machine/'.$uuid.'/describe');
$this->setHeaders('X-Uptrack-User', "MyKspliceUser);
$this->setHeaders('X-Uptrack-Key', "MyKspliceUptrackKey");
return $this->restGet();
}


public function authorizeServer($uuid, $authorized = "true")
{
$data = '{"authorized": '.$authorized.'}';
$this->restClient = new Zend_Rest_Client($this->configuration->host);
$this->setUri($this->baseUri.'/machine/'.$uuid.'/authorize');
$this->setHeaders('X-Uptrack-User', "MyKspliceUser);
$this->setHeaders('X-Uptrack-Key', "MyKspliceUptrackKey");
$this->setRawData($data);
return $this->restPost();
}

}

Thursday, February 18, 2010

WCF to x509 Secured Oracle Service Bus Proxy Service

I just finished a two week struggle creating a .NET 3.5 WCF client that consumed a Java web service hosted on an Oracle Service Bus 10g. The OSB exposed the JWS with a proxy service that included a WS-Policy which required you sign the SOAP header with a Binary Security Token using x509 certificates. During my two week 'adventure' I came across many blogs that pertained to x509 certificates in OSB and WCF security, but none that directly related to my problem. That being, I am going to outline how I was able to solve it.

Issue #1: Although I have 4+ years experience in .NET, I had never used Windows Communication Foundation in .NET. On top of that I was not using the Microsoft VS 2008 suite. Not wanting to shell out $800 for a license, I choose the open source IDE SharpDevelop.

I quickly learned that to generated my WCF proxy client I would be using the svcutil.exe application that is included in the Windows SDK installation. The basic usage is:
svcutil.exe http://myosbserver:7001/HelloWorldProxy?wsdl

Issue #2: Unrecognized WS-Policy

OSB runs in WebLogic Server and uses proprietary WS-Policy implementation that BEA included in WLS 9. WCF does not recognize the policies when the proxy client is created using svcutil.exe. Here is the section in the output.config file indicating the unrecognized policy:
<!--    WsdlImporter encountered unrecognized policy assertions in ServiceDescription 'http://service.myosbserver.com/':    -->
<!-- <wsdl:binding name='HelloWorldImplServiceSoapBinding'> -->
<!-- <wssp:Integrity xmlns:wssp="http://www.bea.com/wls90/security/policy">..</wssp:Integrity> -->
<!-- <wssp:MessageAge xmlns:wssp="http://www.bea.com/wls90/security/policy">..</wssp:MessageAge> -->

This is the actual policy that I was using in OSB when I began:
<wsp:Policy wsu:Id="MyPolicy"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wssp="http://www.bea.com/wls90/security/policy"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wls="http://www.bea.com/wls90/security/policy/wsee#part">

<wssp:Integrity SignToken="false">
<wssp:SignatureAlgorithm URI="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<wssp:CanonicalizationAlgorithm URI="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<wssp:Target>
<wssp:DigestAlgorithm URI="http://www.w3.org/2000/09/xmldsig#sha1" />
<wssp:MessageParts Dialect="http://www.w3.org/TR/1999/REC-xpath-19991116">
wsp:GetBody(.)
</wssp:MessageParts>

</wssp:Target>
</wssp:Integrity>
<wssp:MessageAge Age="300"/>
</wsp:Policy>

After taking a few days to figure out the necessary app.config file settings for the System.ServiceModel configuration section here is what I ended up with.
<system.serviceModel>    
<behaviors>
<endpointBehaviors>
<behavior name="secureBehaviour">
<clientCredentials>
<clientCertificate findValue="appkey"
storeLocation="CurrentUser"
storeName="My"
x509FindType="FindBySubjectName" />
<serviceCertificate>
<defaultCertificate findValue="serverkey"
storeLocation="CurrentUser"
storeName="My"
x509FindType="FindBySubjectName" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="CustomBindingForX509">
<security allowSerializedSigningTokenOnReply="true"
authenticationMode="MutualCertificateDuplex"
requireDerivedKeys="false"
securityHeaderLayout="LaxTimestampLast"
includeTimestamp="true"
keyEntropyMode="ClientEntropy"
messageProtectionOrder="SignBeforeEncrypt"
messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
<secureConversationBootstrap />
</security>
<textMessageEncoding messageVersion="Soap11" />
<httpTransport />
</binding>
</bindings>
<client>
<endpoint address="http://txslaquafp1.nss.vzwnet.com:7001/HelloWorldProxy"
binding="customBinding" behaviorConfiguration="secureBehaviour"
bindingConfiguration="CustomBindingForX509"
contract="HelloWorld" name="HelloWorldImplServiceSoapBindingQSPort" >
<identity>
<certificateReference findValue="serverkey"
storeLocation="CurrentUser"
storeName="My"
x509FindType="FindBySubjectName"/>
</identity>
</endpoint>
</client>
</system.serviceModel>

At this point I attempted my first call to the web service - FAILURE!

Unknown to me at the time was that the .NET Framework 3.5 SP1 by default has a message protection level of Sign and Encrypt, but my WS-Policy only expects the SOAP body to be sign - not encrypted. The result was me having to add an attribute to the Service Contract in the generated proxy client.

Before:
[System.ServiceModel.ServiceContractAttribute(Namespace="http://service.myosbserver.com/", ConfigurationName="HelloWorld")]

After:
[System.ServiceModel.ServiceContractAttribute(Namespace="http://service.myosbserver.com/", ConfigurationName="HelloWorld",
ProtectionLevel = System.Net.Security.ProtectionLevel.Sign )]

Now my outgoing SOAP Header has a signed body without encryption. Oh, but wait, another snag...the SOAP response header is throwing an error! WHAT? .NET also expects the timestamp to be signed on the response as well. (More info) I tried a to get the client application to not require the timestamp to be signed, but after many hours I decided to modify the WS-Policy on the OSB. I added the timestamp signature to the policy to solve this issue. Here is the new WS-Policy on OSB:
<wsp:Policy wsu:Id="MyPolicy"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wssp="http://www.bea.com/wls90/security/policy"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wls="http://www.bea.com/wls90/security/policy/wsee#part">

<wssp:Integrity SignToken="false">
<wssp:SignatureAlgorithm URI="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<wssp:CanonicalizationAlgorithm URI="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<wssp:Target>
<wssp:DigestAlgorithm URI="http://www.w3.org/2000/09/xmldsig#sha1" />
<wssp:MessageParts Dialect="http://www.w3.org/TR/1999/REC-xpath-19991116">
wsp:GetBody(.)
</wssp:MessageParts>

</wssp:Target>
<wssp:Target>
<wssp:DigestAlgorithm URI="http://www.w3.org/2000/09/xmldsig#sha1" />
<wssp:MessageParts
Dialect="http://www.w3.org/TR/1999/REC-xpath-19991116">
wsp:GetHeader(./wsse:Security/wsu:Timestamp)
</wssp:MessageParts>

</wssp:Target>
</wssp:Integrity>
<wssp:MessageAge Age="300"/>
</wsp:Policy>

Just when I think I was done, I had one last error - The certificates were loaded in my personal certificate store on Windows XP, but they were not included in the Trusted Root certificates. Not sure why this matters, but once I also added them to the Trusted Root certificate store the issue resolved.

Non .NET Issues:

OSB/WebLogic Uses Java Keystores (.jks) to hold the private and public x509 certificates used to sign the SOAP header. I had to convert the JKS files to import into the Windows XP certificate stores. Here is a good post that can help with that. Also available is a Java application called Portecle.

The Java Web Service did not contain @WebMethod and @WebParam annotations in the code. WCF in turn was not able to decipher the method names and parameter names from the WSDL. The outgoing SOAP request in turn did not include the the param or method names in the call. We added the annotations to the web service and this issued was resloved. Here is a link to the blog that described this issue.