SF2: Apache2 based HTTP-Authentication with Capifony

Almost every RoR dev knows Capistrano. It’s an awesome tool for deploying RoR applications (not only, but let’s leave it at that for know😉 ) and also as a Symfony2 dev one can harness its power with Capifony.

I usually create Apache2 based HTTP-Authentication by hand for projects where the customer doesn’t provide a dedicated staging server but wants me to deploy on their live-system without going public. But in one of my current projects htpasswd wasn’t available in the restricted SSH-Shell, I could only use their web-based “Directory Security Tool”. That was fine for the first deploy, but afterwards I was always greeted with a 500 server error. But why?

Capifony creates a directory structure for different deployed versions of your application and points via a current named symlink (usually)  to the latest version (example taken from Capifony.org):

`-- /var/www/my-app.com
  |-- current → /var/www/my-app.com/releases/20100512131539
  |-- releases
    |-- 20100512131539
    |-- 20100509150741
    `-- 20100509145325
  `-- shared
    |-- log
    |-- config
      `-- databases.yml
    `-- web
      `-- uploads

SF2 documentation recommends to point the vhost to the /web folder of your application (see SF2 Documentation):

Last but not least, on the production servers, you should point your web root directory to the web/ directory to secure your installation and have an even better looking URL:

http://localhost/demo/hello/Fabien

The “Directory Security Tool” updated my (shared) .htaccess with default Apache2 authentication directives but saved the .htpasswd file with the login credentials IN the applications /web folder. Now on the next deploy the current symlink was changed to the new version, .htaccess stayed the same (as it was shared), but .htpasswd was gone (at least for apache according to .htaccess)!! The tool added an AuthUserFile parameter that linked to a .htpasswd file in the same directory as .htaccess. With the new deploy that file obviously was in another directory as it wasn’t defined as shared in Capifony.

It took me quite some time to find that error, so I decided to write some custom Capifony tasks that should help me configure apache based HTTP authentication more easily, which I’d like to share now:

namespace :deploy do
    namespace :auth do

        def file_helper(append = false)
            require 'tempfile'

            htaccess = "#{deploy_to}/shared/web/.htaccess"
            auth = <<EOS
AuthUserFile #{deploy_to}/.htpasswd
AuthGroupFile /dev/null
AuthName ByPassword
AuthType Basic
require valid-user
EOS

            Tempfile.open('foo') do |file|
                # Fetch the content of the remote file
                get "#{htaccess}", file.path
                # save the contents temporarily so the
                # auth part can be appended
                content = file.read
                if append then
                    # check if it's already enabled
                    if content.scan(auth).size == 0 then
                        content << auth
                    end
                else
                    content.gsub!(auth, "")
                end
                # and now send it back
                put content, "#{htaccess}"
            end
        end
        desc "Enables apache HTTP authentication"
        task :enable do
            file_helper(true)
        end

        desc "Disables apache HTTP authentication"
        task :disable do
            file_helper
        end


        namespace :user do
            htpasswd = "#{deploy_to}/.htpasswd"
            desc "Add a user to the .htpasswd file"

            task :add do
                user = ENV['user']
                password = ENV['password']
                if user.nil? || password.nil? then
                    puts "Usage: deploy:auth:user:add user=$name password=$value"
                    exit
                end
                # remove the user first
                deploy.auth.user.del user
                run "echo '#{user}:" + password.crypt((rand(99-10)+10).to_s) + "' >> #{htpasswd}"
            end

            desc "Deletes a user from the .htpasswd file"
            task :del do
                user = ENV['user']
                if user.nil? then
                    puts "Usage: deploy:auth:user:add user=$name"
                    exit
                end
                run "if [ -e #{htpasswd} ]; then sed -i /^#{user}/d #{htpasswd}; fi"
            end

            desc "Clears the .htpasswd file"
            task :clear do
                run ":> #{htpasswd}"
            end
        end
    end
end

By simply adding the code above to e.g. /app/config/deploy.rb five tasks are added:

  • deploy:auth:enable
  • deploy:auth:disable
  • deploy:auth:user:add user=NAME password=PASSWORD
  • deploy:auth:user:del user=NAME
  • deploy:auth:user:clear

The .htpasswd file will be created at the :deploy_to location, .htaccess at :deploy_to/shared/web/.htaccess, but both can be changed easily. Usage is pretty straightforward:

# create user foo with password bar
cap deploy:auth:user:add user=foo password=bar
# enable http authentication
cap deploy:auth:enable
# ...
# Profit!

I’m open for suggestions and improvements, just drop a line or two.😉

References

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: