Android Client for Chef: Cyllell
Join the DZone community and get the full member experience.
Join For FreeTL;DR; – Download it here.
When you have a server estate the size of DataSift you need a configuration management platform to keep track of what does what, how it is configured and to assist you in rolling out 10 new memcached servers with an identical configuration to the others in production at the drop of a hat. To do that we use Chef from OpsCode.
To interact with Chef you can either use the Web Interface or the CLI client called Knife.
Knife is a powerful command-line interface (CLI) that comes with Chef.
It is used by administrators to interact with the Chef Server API and the local Chef repository. It provides the capability to manipulate nodes, cookbooks, roles, databags, environments, etc., and can also be used to provision cloud resources and to bootstrap systems.
I love CLI’s and prefer them to GUI’s whenever I get the choice but
I’m a sucker for writing an Android app if there’s an API available (much like with Rhybudd for Zenoss).
So some time ago I set about creating an Android application that could
emulate some of the features of Knife from the convenience of your
phone.
The biggest hurdle was authenticating with the API as it uses MixLib::Authentication which according to the documentation provides a class-based header signing authentication object. This signed header object is then sent via HTTPS (usually self signed) and has to be within a strict time window.
Looking at the Wiki documentation regarding authenticated API requests it seems quite straight forward but when you discover that versions of Android below 2.2 don’t even have a Base64 encode function you know it’s going to be a bit of a slog.
Making the authenticated headers requires three key steps, first the cryptographic signing of the Headers with your private key;
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(this.PrivateKey.getBytes(),0));
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey pk = kf.generatePrivate(spec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] EncryptedStream = new byte[cipher.getOutputSize(dataToSign.length())];
try
{
cipher.doFinal(dataToSign.getBytes(),0,dataToSign.length(), EncryptedStream,0);
}
catch (ShortBufferException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return Base64.encodeToString(EncryptedStream, Base64.NO_WRAP);
Secondly creating a variety of headers with other information;
Headers.add(new BasicNameValuePair("Accept","application/json"));
Headers.add(new BasicNameValuePair("Content-Type","application/json"));
Headers.add(new BasicNameValuePair("X-Ops-Sign","version=1.0"));
Headers.add(new BasicNameValuePair("X-Ops-Userid",this.ClientName));
Headers.add(new BasicNameValuePair("X-Ops-Timestamp",TimeStamp));
Headers.add(new BasicNameValuePair("X-Ops-Content-Hash",Disgesteriser.hash_string(Body)));
signed_canonicalize_request = SignHeaders("Method:GET"+
"\nHashed Path:" + Disgesteriser.hash_string(Path) +
"\nX-Ops-Content-Hash:"+Disgesteriser.hash_string(Body)+
"\nX-Ops-Timestamp:"+TimeStamp+
"\nX-Ops-UserId:"+this.ClientName);
Finally the cyrptographic signature needs to be added to the headers
with the proviso that each line can’t be more than 60 characters;
while(rubyLength < 61 && charLocation < signed_canonicalize_request.length())
{
if(signed_canonicalize_request.charAt(charLocation) != '\n' && signed_canonicalize_request.charAt(charLocation) != '\r')
{
AuthString += signed_canonicalize_request.charAt(charLocation);
rubyLength++;
}
charLocation++;
}
Headers.add(new BasicNameValuePair("X-Ops-Authorization-"+Integer.toString(AuthorizationIteration),AuthString));
With all the authenticating work done getting the information is
quite trivial from then on. As described on the OpsCode wiki the API
endpoint for a list of cookbooks is simply /cookbooks.
String Path = "/cookbooks";
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
The App is styled in the same manner as the OpsCode website;
A Read Only functionality Beta version is available on Google Play now;
Any feedback can be sent to @NetworkString, Gareth@NetworksAreMadeOfString.co.uk or by sending ICMP packets padded with your suggestion to 2a01:348:18e:2::2
Published at DZone with permission of Gareth Llewellyn, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Scaling Site Reliability Engineering (SRE) Teams the Right Way
-
Hiding Data in Cassandra
-
Docker Compose vs. Kubernetes: The Top 4 Main Differences
-
10 Traits That Separate the Best Devs From the Crowd
Comments