Tom Says: Safe code is boring code! Why??
Previous page:
Daily Crap 2008-03-17
Next page:
PLOrk Spring 2008 Performance
I don't know very much about security, but I do know enough to understand that no matter how secure I think I've made my blog, there are many vulnerabilities that I am too ignorant to account for. That's why I've done away with web-based blog administration this time around. I've been wanting to do this for a long time — I've always thought that using an unprivileged SSH account would be much more secure than trying to roll my own authenticated session layer onto HTTP.
Getting that to work was much easier than I expected. Whenever you connect to a server over SSH, the server executes your login shell, which is just whichever program is pointed to in /etc/passwd. When you connect with extra commands as in ssh post@alltom.com abc xyz, the login shell is still executed, but with command-line arguments -c "abc xyz". Typical shells like bash interpret this as "execute abc xyz and quit afterward" but when the shell is a Ruby script, you can do whatever you want with it!
Thanks to a little inspiration I got recently in RailsFS [1] (namely how easy it is to serialize and deserialize models using YAML), I was able to quickly put together a pseudo-shell which allows me to post to my blog in sessions almost* like this:
$ ssh post@alltom new_page > draft $ cat draft --- slug: title: allow_comments: true body: $ vim draft ... $ cat draft --- title: body: <p>Hey, guys!</p> $ ssh post@alltom create_page < draft title cannot be blank $ vim draft ... $ cat draft --- title: First Post Over SSH! body: <p>Hey, guys!</p> $ ssh post@alltom create_page < draft Page saved with id 123 and slug "first-post-over-ssh"
Editing a page is not much different, though to lessen the typing, I've aliased at to ssh post@alltom on my shell (as I don't use atd):
$ at get_page first-post-over-ssh > draft $ cat draft --- slug: first-post-over-ssh title: First Post Over SSH! allow_comments: true body: <p>Hey, guys</p> $ vim draft ... $ at update_page first-post-over-ssh < draft Saved page with slug "first-post-over-ssh"
As you can imagine, the code is really simple. It might* look like this (stolen and modified from the TAllTom source [2]):
#!/usr/bin/env ruby
require "rubygems"
require "activerecord"
require File.join(File.dirname(__FILE__), "models")
ActiveRecord::Base.establish_connection(
YAML::load(File.open(File.join(File.dirname(__FILE__), "database.yml")))
)
ActiveRecord::Base.logger = Logger.new(nil)
def show_errors(m)
$stderr.puts m.errors.full_messages.join("\n")
exit 1
end
case action
when "new_page"
puts Page.new.attributes.to_yaml
when "create_page"
page = Page.create(YAML::load($stdin.read))
show_errors(page) if page.new_record?
when "get_page"
puts Page.find_by_slug(args).attributes.to_yaml
when "update_page"
page = Page.find_by_slug(args)
show_errors(page) unless page.update_attributes(YAML::load($stdin.read))
end
There are some obvious issues (SSH connections can be slow to create, the script lacks useful error messages in some cases, etc.), but it seems pretty solid to me. Since the input and output is all done in YAML, it should be dead-simple to create fancier interfaces on top of this.
Since I'm forced to use this interface full time for this blog, I'll find out how well it works soon enough. But I think I've written my last web-based administration interface.
* Posting to AllTom is not as easy as this because the body of each page is stored in a separate table from the entry for the page itself (because I maintain a revision history for each page). With a little extra code in the post script, though, this less of a problem than it may seem.
If you hadn't noticed, YAML fed to the server is parsed into a hash and sent to new or update_attributes directly. This means that it has one of the lovely properties that values you don't specify will be left alone. You can safely update the title of a post on the command-line without first fetching the entire post from the server:
$ at update_page title: New Title
'Course I knew that the idea wasn't original, but I had no examples. Now I do: git-shell [3] looks llike an excellent application of the idea. Set an account's shell to git-shell and it can only be used for interacting with git [4] repositories. What other examples are there of this out there?
Posted Mar 16, 2008, in the late, late night. Updated updated Mar 30, 2008, in the afternoon: Added reference to git-shell.