Non-interactive database migration of Kayako 3 to 4


June 17th, 2011

The Kayako 3 -> 4 upgrade process is a little convoluted. You have to install a fresh copy of 4, then run a script to import your Kayako 3 data. For large installs you need to run the script multiple times to fully migrate your database, which is a problem because the script interactively asks for your database credentials every time it's run. You don't really want to babysit the multi-hour migration process do you? Fear not, just patch the code:

--- __swift/modules/base/console/class.Controller_Import.php.orig    2011-06-16 12:09:40.000000000 +1000
+++ __swift/modules/base/console/class.Controller_Import.php    2011-06-16 12:11:06.000000000 +1000
@@ -75,12 +75,12 @@
        $this->Console->WriteLine('====================', false, SWIFT_Console::COLOR_GREEN);
        $this->Console->WriteLine();
 
-       $_databaseHost = $this->Console->Prompt('Database Host:');
-       $_databaseName = $this->Console->Prompt('Database Name:');
-       $_databasePort = $this->Console->Prompt('Database Port (enter for default port):');
-       $_databaseSocket = $this->Console->Prompt('Database Socket (enter for default socket):');
-       $_databaseUsername = $this->Console->Prompt('Database Username:');
-       $_databasePassword = $this->Console->Prompt('Database Password:');
+       $_databaseHost = 'localhost';
+       $_databaseName = 'kayako3database';
+       $_databasePort = '3306';
+       $_databaseSocket = '';
+       $_databaseUsername = 'kayako3user';
+       $_databasePassword = 'sekret';
 
        if (empty($_databasePort))
        {

This post brought to you by too long spent trying to automate this with an expect script, before discovery of the fact Kayako don't encode all their PHP.

Backslash in username or CWD breaks Bash prompt in Centos


May 18th, 2011

Something I just ran in to. If your username or current directory has an escape code in it (say, because your username is from Active Directory like "DOMAIN\alex.jurkiewicz"), the default Bash shell on Centos 5 has problems. Depending on the escape code you might get a broken prompt or even no terminal output at all!

The problem is that PROMPT_COMMAND set in /etc/bashrc is set to interpret escape codes in the username and current directory:

PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}\007"'

PROMPT_COMMAND is run each time before the prompt is printed. Here it is used to set the xterm or GNU screen window title.

There are two ways to fix this:

  1. Add 'unset PROMPT_COMMAND' to your .bashrc. This will stop your xterm / screen title from being updated but is a simple fix.
  2. Set PROMPT_COMMAND properly using override files in /etc/sysconfig, so $USER and $PWD are echoed literally without escape code interpretation. Create the following two files with +x permissions:

/etc/sysconfig/bash-prompt-xterm:

# Duplicate of default PROMPT_COMMAND, but using a single command to stop race conditions and without escape code interpretation for USER, HOSTNAME and PWD
printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"

/etc/sysconfig/bash-prompt-screen:

# Duplicate of default PROMPT_COMMAND, but using a single command to stop race conditions and without escape code interpretation for USER, HOSTNAME and PWD
printf "\033_%s@%s:%s\033\\" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}"

(The printf statement was taken from this RH bug.)

Logging out and back in again should result in a fixed terminal.

SplashID Sucks


August 22nd, 2010

After an evaluation of SplashID (made by SplashData) as a new password manager for my workplace I've come to the conclusion it's snake oil rather than secure. And not just snake oil, but poorly designed snake oil. Here's why.

The architecture of SplashID is simple. The backend is a plain MySQL database. The user interface is SplashID's app, available on Windows and Mac. When you start your client and log in, it communicates directly in MySQL-speak to the database backend. The connection to MySQL is SSLified (yay!), although bizzarely SplashData call this encryption "IPSec". Not having an actual server process between the clients and database is an unusual design, but it's possible to build something secure this way so we press on.

In SplashID's world, each user's access credentials are made up of three parts. Since each user has a MySQL account, the first two are a username and password. The third part is a "master password". What's a master password? I'm glad you asked. You see, every cell of data in the SplashID database is encrypted with the same key. (Encrypted with AES-256 and Blowfish. Why use two ciphers? Why not!) The encryption key is, of course, the "master password". Because all data is encrypted with this key, every user has to have access to it. Most programs do this by storing the "master password" in the database, one copy per user, encrypted with that user's password. Unlike these programs, SplashID just makes every user remember piece of secret information. Why SplashID does this is another mystery, and a strike against them for poor UI design.

Let's investigate this "every user is a MySQL user" concept. I've created a limited user for myself in SplashID with no access to any passwords. The Splash client app obviously lets me see nothing, but how about a generic MySQL client?

Inappropriate syntax highlighting turn on!

$ mysql -u user1 -p -h splashtest
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 92 to server version: 5.1.47-community
 
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| splashiddb         |
+--------------------+
3 rows in set (0.02 sec)
 
mysql> use splashiddb;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
 
Database changed
mysql> show tables;
+-----------------------+
| Tables_in_splashiddb  |
+-----------------------+
| apppreftable          |
| attachmenttable       |
| columninfotable       |
| customicontable       |
| customtypetable       |
| databaseinfotable     |
| eventloggertable      |
| groupsubgrouptable    |
| grouptable            |
| mostviewedtable       |
| recentlyaddedtable    |
| recentlymodifiedtable |
| recentlyviewedtable   |
| recordtable           |
| typetable             |
| usergrouptable        |
| usertable             |
+-----------------------+
17 rows in set (0.02 sec)

It looks like there's only one table that all passwords are stored in. MySQL doesn't offer per-row access controls, but surely I can't view every password in the database with my limited user???

mysql> select * from recordtable\G
*************************** 1. row ***************************
RECORDID: 1F6642A0C892BC76
TYPEUID: 0000000000000013
GROUPUID: 1F66429EC892BBDB
FIELD1: <blob>
FIELD2: <blob>
FIELD3: 
FIELD4: 
FIELD5:
FIELD6:
FIELD7:
FIELD8:
FIELD9: <blob> 
FIELD10: <blob>
NOTE:
HASATTACHMENT: 0
HASCUSTOMFIELD: 0
VIEWCOUNT: 6
*************************** 2. row ***************************
[snip]
10 rows in set (0.03 sec)

Oh dear. Oh dear oh dear.

mysql> Bye

So there you go. Every user in SplashID, no matter how limited, can view every password in the database, all encrypted with a key they know. Another strike against SplashID. They need a miracle now.

And hark! Here comes the explanation from SplashData. I emailed them specifically regarding my findings, wanting to make sure this wasn't some huge mistake. I asked:

...every cell is encrypted using the same process, right? From that it follows that if a user can decrypt one cell, they can decrypt every cell. The only protection is that your encryption routine is not published. Or am I missing something?

The reply:

That’s right Alex.

That’s why I mentioned-
> Actually, they don't use the same key. AES key is a hash function of the
> Blowfish key. I'm sorry I cannot give you more details on the algorithms we use.

So, if the user knows the Blowfish key, it is not enough. They still need to decrypt using SplashID Enterprise application.

Even though every user can download the entire encrypted database, even though the master password decryption routine is stored in the client side application, it's all fine because nobody has ever reverse engineered an application to extract a single hash function before! In the end, the previous security missteps hardly matter compared to this blunder. All it will take is one enterprising security researcher or blackhat to figure it out and put their findings on the web, and suddenly every password in every SplashID install is wide open for the taking by its users.

We won't be using SplashID at my workplace, and my advice to you, dear reader, is to avoid them too.

Wordpress's WP-Super-Cache's Super Cache with nginx


May 12th, 2010

(Apologies for the triple-layer title, but it's a specific subject involving a badly named plugin.)

This has been explained before (the progenitor for most other examples on the net seems to be this forum post), but the solution was ugly and slightly incomplete. nginx's lack of a one-line RewriteCond equivalent means there will never be an elegant solution, but I think I've come up with something clearer.

First, background. WP Super Cache has two levels of caching:

  1. "WP Cache". Whenever Wordpress's index.php renders a page, a copy of the page output is stored in /blog/wp-content/cache (and the meta subfolder). For future requests for the same page, this cached copy is served by index.php. The good: subsequent requests don't hit the database or re-run your badly coded widgets for every visitor. The bad: PHP still runs for every request.
  2. "Super Cache". As well as a copy of page output being stored as per above, a copy is also stored in /blog/wp-content/supercache, in a structure that mirrors your blog's URL hierarchy. With clever use of rewrite rules at your webserver layer, you can entirely skip loading PHP & Wordpress for any request that a cached file has been created for.

The WP Cache layer always works. The rest of this post is about making use of the Super Cached files with your shiny nginx server. For reference, the Apache rules are here. This nginx code follows the same order and structure, but has some differences. Read:

location /blog {
    gzip_static on;

Aside: gzip_static requires an nginx configured with --with-http_gzip_static_module. If your build isn't, and you don't want to compile your own, just remove this directive. Instead of serving pre-compressed Super Cache files to clients that support compression, nginx will compress them on the fly (like normal).

    set $supercache "";
    if ($request_method = GET) {
        set $supercache "${supercache}G";
    }
    if ($args = "") {
        set $supercache "${supercache}A";
    }
    if ($http_cookie !~ (comment_author_|wordpress_logged_in|wp-postpass_)) {
        set $supercache "${supercache}C";
    }
    if (-f $document_root/blog/wp-content/cache/supercache/$http_host$request_uri/index.html) {
        set $supercache "${supercache}F";
    }
    # If we met all the conditions, serve the supercached file
    if ($supercache = GACF) {
        rewrite ^ /blog/wp-content/cache/supercache/$http_host$request_uri/index.html break;
    }
    # Otherwise pass to wordpress as normal
    if (!-e $request_filename) {
        rewrite ^ /blog/index.php last;
    }
}

# The cache files should not be directly accessible to clients
location /blog/wp-content/cache { internal; }

# Configure the PHP backend as per normal
location ~ (\.php$) {
    include fastcgi_params;
    if (-e $request_filename) {
        fastcgi_pass unix:/tmp/nginx-php-fastcgi.sock;
    }
}

Done! If you have problems, three pointers:

  1. WP Super Cache has a very big settings page. You can set them as you like mostly, but make sure you set this and this (if you're using gzip_static).
  2. Check the bottom of the source of your pages to see if a page was server from the cache, and if so, whether it was served from the Super Cache.
  3. If you need to troubleshoot, make liberal use of the logging facility that WP Super Cache implements.

Misc