Sensitive Configuration and AppHarbor

Recently I’ve been developing an application for my own personal use and I’ve been hosting it on AppHarbor. If you haven’t heard of AppHarbor it is a hosting/deployment solution for .NET web applications that works on distributed version control system (git by default but you can hook it up to hg-git for mercurial support). Essentially AppHarbor gives you a git repository in the cloud (and a web server, and a database server if you like).

You work locally making commits as you go. When you are ready you add AppHarbor as a git remote and push to it. The AppHarbor infrastructure notes the change to your AppHarbor repository, checks out the code, builds it, tests it and if it’s all good it gets automatically deployed to a web server. If you aren’t happy with the result you can roll your application back to a previous version by literally clicking a single link. If you haven’t checked it out I highly recommend it.

AppHarbor deployment

One of the things I’m doing in my app is talking to a 3rd party API (TheMovieDb – which is like an open IMDB clone with wiki-like tendencies). In order to access the API I need to provide an API-Key. This key is linked to my identity and I don’t want really want to check it into source control as this is potentially unsafe (especially if I decide to open source the project down the track). Luckily AppHarbor has an answer. In the AppHarbor dashboard for my app there is a section for Configuration Variables.

AppHarbor Configuration Dashboard

Basically you start by creating an entry in the appSettings configuration section of my web.config for the API-Key. Next you can create a corresponding AppHarbor Configuration Variable with the same name as the appSetting entry. Now, at deployment time AppHarbor will replace the value in the deployed web.config with the value I set up in the dashboard. There full details about how to do this here.

AppSettings for AppHarbor app

This still leaves you with an issue though. If you want to develop and test locally you need to put your real API-Key in the web.config. Each time before pushing changes you need to remember to remove the API-Key or you would accidently be sending your private data to the cloud. In fact, because pushing changes between repositories pushes each changeset you would need to remove the key before each check-in! No good.

To get around this issue I created a simple configuration service that checks for a special key, in this case {ENV}. If it finds this key then it will look in the systems Environment Variables for the value instead. This has an added advantage that my code is now decoupled from the config file as well.

public interface IConfig
{
    string Get(string key);
}

public class Config : IConfig
{
    public string Get(string key)
    {
        var fromConfig = ConfigurationManager.AppSettings[key];
        if (String.Equals(fromConfig, "{ENV}", StringComparison.InvariantCultureIgnoreCase))
        {
            return Environment.GetEnvironmentVariable(key);
        }
        else
        {
            return fromConfig;
        }
    }
}

public class TheMovieDbService
{
    private readonly string _apiKey;

    public TheMovieDbService(IConfig config)
    {
        _apiKey = config.Get("TheMovieDB_API_KEY");
    }
}

<!-- usage -->
<configuration>
    <appSettings>
        <add key="TheMovieDB_API_Key" value="{ENV}"/>
    </appSettings>
</configuration>

Now when I’m writing code on my laptop, the API-Key comes from my local environment (if you can get to that, you already have my identity) but when the app is running in the cloud it uses the value configured from the AppHarbor dashboard.

Environment variables with TheMovieDb API Key

IT’S A TRAP!: When you first create a new environment variable for use in your application you will need to restart Visual Studio. If you don’t then your app will get the old version of the environment variables when it’s run (i.e. it will get the set that Visual Studio started with).

If you’re interested, the app I wrote randomly selects a science fiction movie released over a particular date range. Try it out here if you’re looking for something to watch. Getting this deployed on AppHarbor took about 5 minutes (from File > New Project to appearing in my browser). After that each deployment took less than 30 seconds.

Posted by: Mike Minutillo
Last revised: 27 May, 2011 04:04 PM History

Comments

01 May, 2011 10:11 AM @ version 0

That's a nice post.
Maybe it's just me, but I'd like to write my thoughts about the design:
I'd split Config class into 3 classes:
(1) WebConfig : IConfig
(2) EnvConfig : IConfig
(3) ConfigChain : IConfig

The ConfigChain gets an array of IConfig-s (injection by constructor, for instance), and tries to get the value from them in the order of the array.

Best,

--Ron

20 Apr, 2011 04:08 AM @ version 0

You can put the config file one level above the git repository's root then you cant check it in.

19 Apr, 2011 11:22 PM @ version 0

Nice :-)

One thing to note with @Morgan's suggestion (which is also a good one) is that in a team environment someone will likely check in override.config by accident at some point unless it is explicitly added to the .gitignore or .hgignore file.

18 Apr, 2011 06:40 AM @ version 0

Yep, that'd do it too. I forgot you could do that :)

17 Apr, 2011 06:34 PM @ version 0

There is another option that requires no code, only xml :)
In your app.config file you add an attribute to appSettings named file and the value is a path to an config files with overrides for your app condig file.
ex:
<!-- app.config -->
<configuration>
<appSettings file="override.config">
<add key="TheMovieDBAPIKEY" value="notrealkey" />
</appSettings>
</configuration>
and override.config looks like
<appSettings>
<add key="TheMovieDBAPIKEY" value="Therealkey" />
</appSettings>

And dont check in your override.config file :)

No new comments are allowed on this post.