My colleague Rich Mills created a great post about a script to get PEM certs into databags. To sum up, the major issue was that the white space was interfering with the knife upload command. The knife script uses the ruby, some chef gems, ruby file manipulation, and the chef api to properly create the databag skipping the knife upload. Seizing on this pattern, I decided to use it to manage the p12 certs on my current project. I copied the krb script locally and ran it. It appeared as though it worked, and I could see the cert in the databag in binary form. I did the standard victory dance and was ready to buy Rich a drink for saving me a days worth of work. Feeling confident I run my chef script to place the certs aaaaaannnndd there is a small chef explosion. I was greeted with the following error:
FATAL: Yajl::ParseError: lexical error: invalid bytes in UTF8 string. {"json_wrapper":"0\b\u0007 \u0003\u0002\u000 (right here) ------^
The type of PEM file Rich was working with is text at heart. The cert is encoded as base64 text with some plain text metadata. The only issue that Rich was having had to do with white space. After analyzing the error and the contents of the file I made an educated guess that since a p12 is a binary file, there is probably a character in the p12 cert that the json lexical analyzer is throwing an error on while decoding the databag. To test this theory I took the sane cert and base64 encoded it. I then used Rich’s script to upload it and ran the chef script again. While my explanation may not be 100% correct my chef script is now executing correctly with the base64 text. Unfortunately, I now have two issues:
1. I need to base64 encode the cert
2. The cert is not usable in the format my script is leaving it at the moment.
To solve the whole problem we need to make a few assumptions. First you need to have the ruby base64 library and secondly you need to have a command line base64 utility on the system. To solve the first issue we need to change the knife script. Since we are encrypting the files, we are already reading them into the knife script. On line 41 of the original script, sure enough, we are reading in the whole file. With a slight modification from:
content = File.read(filename) bag_item[:content] = content
to:
content = File.read(filename) base64_content = Base64.encode64(content) bag_item[:content] = base64_content
We will now make the encrypted databag contain a base64 version of what ever it is we want to upload. This is great for getting the data in and out of a databag, but now when the data is used I need to account for the base64ness. While I am sure there is many cleaner chef like ways to implement what I want, I was limited on time so I needed to settle for the following chef code in my recipe:
execute "decode #{cert}" do command "base64 -d /tmp/#{cert}.base64 > /tmp/#{cert}.pem" end
And viola, I can use databags to manage my binary files.