Gyong Ju - South Korea

Archive for the ‘Information Security’ Category

It’s been a while since I last posted on the NIST RBAC Data Model and there have some (small) changes that make it a good idea to do a new post on this topic.

I’ve made two changes to the data model:

  • Removed the many-to-many mapping in user/sessions and replaced it with a one-to-many mapping because each session is associated with a single user and each user is associated with one or more sessions.
  • Renamed table “user” to “users” to avoid clashes in PostgreSQL and changed all associated references

You can find the database independent model here as a Dezign for Databases file.

Specific output formats for both MySQL 5 and PostgreSQL 9 are included below.

MySQl 5:

# ---------------------------------------------------------------------- #
# Script generated with: DeZign for Databases v6.3.3                     #
# Target DBMS:           MySQL 5                                         #
# Project file:          rbac_nist_.dez                                  #
# Project name:          nist rbac model                                 #
# Author:                m.e. post                                       #
# Script type:           Database creation script                        #
# Created on:            2011-06-11 21:01                                #
# ---------------------------------------------------------------------- #

# ---------------------------------------------------------------------- #
# Tables                                                                 #
# ---------------------------------------------------------------------- #

# ---------------------------------------------------------------------- #
# Add table "users"                                                      #
# ---------------------------------------------------------------------- #

CREATE TABLE `users` (
    `user_id` INTEGER NOT NULL,
    `username` VARCHAR(40) NOT NULL,
    `password` VARCHAR(64) NOT NULL,
    `nonce` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
    `first_name` VARCHAR(50) NOT NULL,
    `family_name` VARCHAR(100) NOT NULL,
    `email` VARCHAR(100) NOT NULL,
    PRIMARY KEY (`user_id`)
);

CREATE INDEX `IDX_users_1` ON `users` (`username`);

# ---------------------------------------------------------------------- #
# Add table "roles"                                                      #
# ---------------------------------------------------------------------- #

CREATE TABLE `roles` (
    `role_id` INTEGER NOT NULL,
    `name` VARCHAR(100) NOT NULL,
    PRIMARY KEY (`role_id`)
);

CREATE INDEX `IDX_roles_1` ON `roles` (`name`);

# ---------------------------------------------------------------------- #
# Add table "user_role"                                                  #
# ---------------------------------------------------------------------- #

CREATE TABLE `user_role` (
    `user_id` INTEGER NOT NULL,
    `role_id` INTEGER NOT NULL,
    PRIMARY KEY (`user_id`, `role_id`)
);

CREATE INDEX `IDX_user_role_1` ON `user_role` (`user_id`);

CREATE INDEX `IDX_user_role_2` ON `user_role` (`role_id`);

# ---------------------------------------------------------------------- #
# Add table "sessions"                                                   #
# ---------------------------------------------------------------------- #

CREATE TABLE `sessions` (
    `session_id` INTEGER NOT NULL,
    `user_id` INTEGER NOT NULL,
    `name` VARCHAR(64) NOT NULL,
    `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`session_id`)
);

# ---------------------------------------------------------------------- #
# Add table "operations"                                                 #
# ---------------------------------------------------------------------- #

CREATE TABLE `operations` (
    `operation_id` INTEGER NOT NULL,
    `name` VARCHAR(100) NOT NULL,
    `_create` INTEGER DEFAULT NULL,
    `_read` INTEGER DEFAULT NULL,
    `_update` INTEGER DEFAULT NULL,
    `_delete` INTEGER DEFAULT NULL,
    `locked` INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (`operation_id`)
);

# ---------------------------------------------------------------------- #
# Add table "objects"                                                    #
# ---------------------------------------------------------------------- #

CREATE TABLE `objects` (
    `object_id` INTEGER NOT NULL,
    `name` VARCHAR(100) NOT NULL,
    `locked` INTEGER NOT NULL DEFAULT 0,
    PRIMARY KEY (`object_id`)
);

# ---------------------------------------------------------------------- #
# Add table "permissions"                                                #
# ---------------------------------------------------------------------- #

CREATE TABLE `permissions` (
    `permission_id` INTEGER NOT NULL,
    `object_id` INTEGER NOT NULL,
    `operation_id` INTEGER NOT NULL,
    `name` VARCHAR(100) NOT NULL,
    PRIMARY KEY (`permission_id`, `object_id`, `operation_id`)
);

CREATE INDEX `IDX_permission_1` ON `permissions` (`object_id`);

CREATE INDEX `IDX_permission_2` ON `permissions` (`operation_id`);

# ---------------------------------------------------------------------- #
# Add table "role_permission"                                            #
# ---------------------------------------------------------------------- #

CREATE TABLE `role_permission` (
    `role_id` INTEGER NOT NULL,
    `permission_id` INTEGER NOT NULL,
    PRIMARY KEY (`role_id`, `permission_id`)
);

CREATE INDEX `IDX_role_permission_1` ON `role_permission` (`role_id`);

CREATE INDEX `IDX_role_permission_2` ON `role_permission` (`permission_id`);

# ---------------------------------------------------------------------- #
# Add table "session_role"                                               #
# ---------------------------------------------------------------------- #

CREATE TABLE `session_role` (
    `role_id` INTEGER NOT NULL,
    `session_id` INTEGER NOT NULL,
    PRIMARY KEY (`role_id`, `session_id`)
);

CREATE INDEX `IDX_session_role_1` ON `session_role` (`role_id`);

CREATE INDEX `IDX_session_role_2` ON `session_role` (`session_id`);

# ---------------------------------------------------------------------- #
# Foreign key constraints                                                #
# ---------------------------------------------------------------------- #

ALTER TABLE `user_role` ADD CONSTRAINT `user_user_role`
    FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `user_role` ADD CONSTRAINT `role_user_role`
    FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `sessions` ADD CONSTRAINT `users_sessions`
    FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `permissions` ADD CONSTRAINT `object_permission`
    FOREIGN KEY (`object_id`) REFERENCES `objects` (`object_id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `permissions` ADD CONSTRAINT `operation_permission`
    FOREIGN KEY (`operation_id`) REFERENCES `operations` (`operation_id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `role_permission` ADD CONSTRAINT `role_role_permission`
    FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `role_permission` ADD CONSTRAINT `permission_role_permission`
    FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`permission_id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `session_role` ADD CONSTRAINT `role_session_role`
    FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE `session_role` ADD CONSTRAINT `session_session_role`
    FOREIGN KEY (`session_id`) REFERENCES `sessions` (`session_id`) ON DELETE CASCADE ON UPDATE CASCADE;

PostgreSQL 9:


/* ---------------------------------------------------------------------- */
/* Script generated with: DeZign for Databases v6.3.3                     */
/* Target DBMS:           PostgreSQL 9                                    */
/* Project file:          rbac_nist_.dez                                  */
/* Project name:          nist rbac model                                 */
/* Author:                m.e. post                                       */
/* Script type:           Database creation script                        */
/* Created on:            2011-06-11 20:58                                */
/* ---------------------------------------------------------------------- */

/* ---------------------------------------------------------------------- */
/* Tables                                                                 */
/* ---------------------------------------------------------------------- */

/* ---------------------------------------------------------------------- */
/* Add table "users"                                                      */
/* ---------------------------------------------------------------------- */

CREATE TABLE users (
    user_id INTEGER  NOT NULL,
    username CHARACTER VARYING(40)  NOT NULL,
    password CHARACTER VARYING(64)  NOT NULL,
    nonce DATE DEFAULT '0000-00-00 00:00:00'  NOT NULL,
    first_name CHARACTER VARYING(50)  NOT NULL,
    family_name CHARACTER VARYING(100)  NOT NULL,
    email CHARACTER VARYING(100)  NOT NULL,
    PRIMARY KEY (user_id)
);

CREATE INDEX IDX_users_1 ON users (username);

/* ---------------------------------------------------------------------- */
/* Add table "roles"                                                      */
/* ---------------------------------------------------------------------- */

CREATE TABLE roles (
    role_id INTEGER  NOT NULL,
    name CHARACTER VARYING(100)  NOT NULL,
    PRIMARY KEY (role_id)
);

CREATE INDEX IDX_roles_1 ON roles (name);

/* ---------------------------------------------------------------------- */
/* Add table "user_role"                                                  */
/* ---------------------------------------------------------------------- */

CREATE TABLE user_role (
    user_id INTEGER  NOT NULL,
    role_id INTEGER  NOT NULL,
    PRIMARY KEY (user_id, role_id)
);

CREATE INDEX IDX_user_role_1 ON user_role (user_id);

CREATE INDEX IDX_user_role_2 ON user_role (role_id);

/* ---------------------------------------------------------------------- */
/* Add table "sessions"                                                   */
/* ---------------------------------------------------------------------- */

CREATE TABLE sessions (
    session_id INTEGER  NOT NULL,
    user_id INTEGER  NOT NULL,
    name CHARACTER VARYING(64)  NOT NULL,
    created DATE DEFAULT CURRENT_TIMESTAMP  NOT NULL,
    PRIMARY KEY (session_id)
);

/* ---------------------------------------------------------------------- */
/* Add table "operations"                                                 */
/* ---------------------------------------------------------------------- */

CREATE TABLE operations (
    operation_id INTEGER  NOT NULL,
    name CHARACTER VARYING(100)  NOT NULL,
    _create INTEGER DEFAULT NULL,
    _read INTEGER DEFAULT NULL,
    _update INTEGER DEFAULT NULL,
    _delete INTEGER DEFAULT NULL,
    locked INTEGER DEFAULT 0  NOT NULL,
    PRIMARY KEY (operation_id)
);

/* ---------------------------------------------------------------------- */
/* Add table "objects"                                                    */
/* ---------------------------------------------------------------------- */

CREATE TABLE objects (
    object_id INTEGER  NOT NULL,
    name CHARACTER VARYING(100)  NOT NULL,
    locked INTEGER DEFAULT 0  NOT NULL,
    PRIMARY KEY (object_id)
);

/* ---------------------------------------------------------------------- */
/* Add table "permissions"                                                */
/* ---------------------------------------------------------------------- */

CREATE TABLE permissions (
    permission_id INTEGER  NOT NULL,
    object_id INTEGER  NOT NULL,
    operation_id INTEGER  NOT NULL,
    name CHARACTER VARYING(100)  NOT NULL,
    PRIMARY KEY (permission_id, object_id, operation_id)
);

CREATE INDEX IDX_permission_1 ON permissions (object_id);

CREATE INDEX IDX_permission_2 ON permissions (operation_id);

/* ---------------------------------------------------------------------- */
/* Add table "role_permission"                                            */
/* ---------------------------------------------------------------------- */

CREATE TABLE role_permission (
    role_id INTEGER  NOT NULL,
    permission_id INTEGER  NOT NULL,
    PRIMARY KEY (role_id, permission_id)
);

CREATE INDEX IDX_role_permission_1 ON role_permission (role_id);

CREATE INDEX IDX_role_permission_2 ON role_permission (permission_id);

/* ---------------------------------------------------------------------- */
/* Add table "session_role"                                               */
/* ---------------------------------------------------------------------- */

CREATE TABLE session_role (
    role_id INTEGER  NOT NULL,
    session_id INTEGER  NOT NULL,
    PRIMARY KEY (role_id, session_id)
);

CREATE INDEX IDX_session_role_1 ON session_role (role_id);

CREATE INDEX IDX_session_role_2 ON session_role (session_id);

/* ---------------------------------------------------------------------- */
/* Foreign key constraints                                                */
/* ---------------------------------------------------------------------- */

ALTER TABLE user_role ADD CONSTRAINT user_user_role
    FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE user_role ADD CONSTRAINT role_user_role
    FOREIGN KEY (role_id) REFERENCES roles (role_id) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE sessions ADD CONSTRAINT users_sessions
    FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE permissions ADD CONSTRAINT object_permission
    FOREIGN KEY (object_id) REFERENCES objects (object_id) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE permissions ADD CONSTRAINT operation_permission
    FOREIGN KEY (operation_id) REFERENCES operations (operation_id) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE role_permission ADD CONSTRAINT role_role_permission
    FOREIGN KEY (role_id) REFERENCES roles (role_id) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE role_permission ADD CONSTRAINT permission_role_permission
    FOREIGN KEY (permission_id) REFERENCES permissions (permission_id) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE session_role ADD CONSTRAINT role_session_role
    FOREIGN KEY (role_id) REFERENCES roles (role_id) ON DELETE CASCADE ON UPDATE CASCADE;

ALTER TABLE session_role ADD CONSTRAINT session_session_role
    FOREIGN KEY (session_id) REFERENCES sessions (session_id) ON DELETE CASCADE ON UPDATE CASCADE;

Now that’s a big claim but I can assure you its true for all three aspects. It doesn’t even require heavy customisation and the approach is based on standard plugins available on the WordPress plugin site. However like everything there’s a trade off with the approach and in this case its the loss of flexibility and dynamic behaviour. This isn’t an issue with static websites but if you’re running a blog then this solution isn’t for you (stuff like comments won’t work as this requires connectivity and feedback from WordPress). It’s up to you to decide whether my approach has merits for your use case. I offer no guarantees other than that I have applied the approach below to my own systems and for me it works. It’s very rough around the edges, I have been hacking some files and I haven’t rolled my changes into a nice shrink wrap form. Enough with the disclaimers let’s get going with an actual explanation of what I’m offering.

Security

WordPress suffers from the same problem that almost all Content Management Systems (CMS) suffer from, it has a unified code base for both content publication and content management. With WordPress (and similar systems) that share the same code base it is possible to hack the content management system through the content publication system. The content publication system is the aspect of the CMS that generates the pages if a visitor hits the site. The content publication system by its very nature is an open interface to the outside world and can therefor be hacked. By the fact that it shares code with the CMS system it is inevitable that also the CMS can be compromised in an attack on the content publication system. These hacks occur time and again and are endemic to the shared code approach so they will never go away. The only way of ensuring your CMS is not hacked through your content publication system is by separating the two. Now separation in a physical (code) sense is possible but requires a huge amount of effort and in effect means a different version of WordPress through a fork. This is not what I want to achieve, I have limited time and I can’t maintain my own version of WordPress and keep up with all the new functionality that the WordPress team cranks out all the time. Therefore I mean separation in a logical sense and this I achieve through the use of WP SuperCache. WP Super Cache turns your WordPress site/blog into a collection of static pages and it uses a .htaccess mod_rewrite approach to serve customers the static pages. It also has an option to serve page components like JS, CSS and images from a Content Delivery Network (CDN). My approach to separating the CMS from content publication is that I turn the WP Super Cache cache (pardon the pun) into its own virtual host in Apache and serve content in its static form from that Virtual Host. My visitors don’t need to access the WordPress installation to get to the content, the CMS and the content publication are logically separated. Now there’s a couple of tricks required for getting this up and running and I’ll explain these later in this post.

Speed

The approach of moving your page components into a CDN is well known and relatively straightforward to achieve with solutions like WP Super Cache or W3 Total Cache. Going one step further and moving your entire site, so including your html is a little less usual but that is what I have achieved. My test site (not this one) based on the standard twentyten theme now loads in 1.223 seconds of which 0.252 seconds is spent on the DNS lookups. The html and all other page components are served through Amazon Cloudfront using Origin Pull (but any other CDN can do the same, there is no Cloudfront specific trickery involved).

How it works

There’s a couple of code changes involved and some Apache and DNS configuration changes. What do you need:

  • LAMP platform and WordPress. I used the most recent version of WordPress (3.1.2) at the time of writing. Hosting is done on Amazon EC2 with a CentOS 5.6 based system
  • WP Super Cache plugin installed
  • A CDN, I used Amazon Cloudfront
  • Access to DNS for setting CNAME records

I’m assuming you have a functioning LAMP server. The following steps need to be executed:

  • Create a virtual host in Apache for the WordPress site
  • Install WordPress and WP Super Cache plugin
  • Configure the WP Super Cache plugin
  • Code hacks to the WP Super Cache plugin
  • Set up your CDN
  • Configure your DNS
  • Test

We’re going to put the WordPress site in a directory called “wordpress” located in /var/www/html (CentOS/Fedora default) and create a special virtual host called cms.example.com:

<VirtualHost *:80>
ServerName cms.example.com
ServerAdmin admin@example.com
DocumentRoot /var/www/html/wordpress
LogLevel info
ErrorLog logs/error_log
TransferLog logs/access_log
</VirtualHost>

Install WordPress in the /var/www/html/wordpress directory and configure it with the cms.example.com home/site url. Check that the installation completed sucessfully and you can access the admin interface at http://cms.example.com/wp-admin/. Install the WP Super Cache plugin as explained by the documentation.

Configure the WP Super Cache plugin as follows:

  • Advanced settings:
    • Cache hits to this website for quick access
    • Use PHP to serve cache files
    • 304 Not Modified browser caching. Indicate when a page has not been modified since last requested
    • Cache rebuild. Serve a supercache file to anonymous users while a new file is being generated
  • CDN settings:
    • Enable CDN Support
    • Off-site URL: http://cdn.example.com (where example.com is your own domain)
  • Preload settings:
    • Preload mode (garbage collection only on legacy cache files)

Create a new directory in your webroot, e.g. “cache”:

mkdir /var/www/html/cache

Set this up as a new virtual host in Apache, let’s call this new site cache.example.com:

<VirtualHost *:80>
ServerName cache.example.com
ServerAdmin admin@example.com
DocumentRoot /var/www/html/cache/supercache/cms.example.com
ErrorLog logs/error_log
TransferLog logs/access_log
</VirtualHost>

Restart Apache to get the new Virtual Hosts activated. Copy over the wp-content/themes/[theme-name] folder to your cache directory (/var/www/html/cache/supercache/cms.example.com) but only where it concerns css, js and images. You don’t need to copy over the php files as only the web page resources are required. The same applies for the wp-includes directory if your theme uses javascript files in the js subdirectory. Check if the pages come up ok if you access http://cache.example.com. If they do you’re fine, if not troubleshoot what the issue is, e.g. look at the Apache logs/error_log file.

After this we need to do some small code wrangling, it’s going to be ugly but small and we need the absolute path of the directory that we just created. Navigate to the plugin directory of your WordPress installation and enter the wp-super-cache directory. Open file “wp-cache-phase1.php” and at the top of the file just after the include( WPCACHEHOME . ‘wp-cache-base.php’); instruction add:

include( WPCACHEHOME . 'wp-cache-base.php');
$cache_path = "/var/www/html/cache/";

Save the file and open file “wp-cache-phase2.php”. At the top of the file, just after

$cache_path = "/var/www/html/cache/";

In the same file look for function function wp_cache_get_ob(&$buffer) and in this function look for this sequence (around line 504):

 } else {
                $buffer = apply_filters( 'wpsupercache_buffer', $buffer );
                // Append WP Super Cache or Live page comment tag
                wp_cache_append_tag($buffer);

After this sequence add:

$buffer = str_replace("http://cms.example.com", "http://www.example.com", $buffer);

Reason for this is that WP Super Cache will generate pages based on its own site/home url (cms.example.com) and we need to replace this url with the actual site url (www.example.com). Hence the clumsy find and replace whilst the pages are generated by the Preload section of the WP Super Cache plugin. I’m sure it can be done nicer but I’m just proving a concept, not winning prices for clean code.

Set up your CDN so that it has two Distribution Points / Pull Zones or whatever you CDN provider calls them. One should be listening to www.example.com and have cache.example.com as its origin server and the other should be listening to cdn.example.com and also have cache.example.com as its origin server. Note the CNAME records the CDN generates for you, let’s assume the following:

  • xyz.cloudfront.net –> www.example.com
  • abc.cloudfront.net –> cdn.example.com

Go to your DNS setup and set up the following changes:

  • Have the www subdomain (I’m assuming you already have this set up otherwise create a www CNAME record) refer to xyz.cloudfront.net
  • Create a CNAME record for cdn.example.com and have this point at abc.cloudfront.net

Apply the DNS changes and wait for the changes to propagate. If you can do a successful dig on www.example.com and cdn.example.com and you get to see something like this you should be ok:

www.example.com.         3044   IN CNAME  xyz.cloudfront.net.
xyz.cloudfront.net.      60     IN CNAME  xyz.ams1.cloudfront.net.
xyz.ams1.cloudfront.net. 60     IN A      216.137.59.28
xyz.ams1.cloudfront.net. 60     IN A      216.137.59.54
xyz.ams1.cloudfront.net. 60     IN A      216.137.59.64
xyz.ams1.cloudfront.net. 60     IN A      216.137.59.115
xyz.ams1.cloudfront.net. 60     IN A      216.137.59.207
xyz.ams1.cloudfront.net. 60     IN A      216.137.59.216
xyz.ams1.cloudfront.net. 60     IN A      216.137.59.220
xyz.ams1.cloudfront.net. 60     IN A      216.137.59.254

Access your site at http://www.example.com/ and see if its working. If so start doing your performance tests and do some investigations with HTTP analysis tooling like HTTP Fox.

After you’ve established everything works fine you can make cms.example.com only accessible to yourself or your content editors, there is no real time dependency on WordPress anymore and the installation can be purely used for content management activities.

Connecting to my Amazon EC2 image (from which this site is running) from Mac Os X took ages to find out and turned out to be relatively simple with the correct information (isn’t that always the case). At first I didn’t think the builtin Mac OS X ssh could cut it so I started looking into various Mac OS X ssh clients (Fugu, RBrowser, CyberDuck etc ..) but none of those could handle the Amazon public/private key encryption. Then I started looking into using Putty on Mac OS X even though thats not available for Mac OS X (but with a little help from MacPorts). That bombed on problems with GTK1. Dang, what to do?

Continue Reading

A Front End Controller is part of an MVC pattern.

The controller receives input and initiates a response by making calls on model objects. An MVC application may be a collection of model/view/controller triplets, each responsible for a different UI element. MVC is often seen in web applications where the view is the HTML or XHTML generated by the app. The controller receives GET or POST input and decides what to do with it, handing over to domain objects (i.e. the model) that contain the business rules and know how to carry out specific tasks such as processing a new subscription.

Continue Reading

There’s an updated article for the data model

I’m nearing the end of my development work for the first version of the NIST RBAC API for PHP. Rather than trying to explain this myself I quote the Wikipedia page on this and Role Based Access Control (RBAC) in general:

Continue Reading

A good friend of mine asked if it was possible to log out of a Basic Authentication session. My first knee-jerk response was that Basic Authentication has no log out function and you should close the browser to safely log out of the session. After some days silence he came back with a script he’d found on the php.net site. The script used sessions to break the Basic Authentication behavior of the browser. It wasn’t a very successful script because it only worked in a limited set of browsers but it got me thinking about a better solution.

Continue Reading