Access Configuration Data Through a Project Silk Widget
Configuration files are great. They provide a way to isolate sensitive data in a single location that can be protected from the application. In ASP.NET, tools like DPAPI allow you to encrypt elements of your configuration to avoid disclosing sensitive connection strings and settings. In this article, I’ll show how to build a widget (using the Project Silk widget structure) that pulls data from a configuration file. I’ll also explain a few security tricks that help limit access to the data in your configuration files on the client and server.
First, you should review the Project Silk documentation, because Project Silk serves as our foundation. We will build a utility widget, which means the widget is used only to process data, not to present an interface. You can build this widget in two ways:
- Dynamically generate script on the server. This approach uses an ASP.NET handler to generate the JavaScript necessary to work with the configuration values. The values will be embedded in the script, so you won’t need a trip to the server to retrieve them. You can build the JavaScript as a bunch of variables with values or as an object that is referenced throughout your application.
- Build a service that delivers the configuration via an Ajax call. This approach can use the Data Manager from Project Silk to make returning a configuration object simpler. You could create the configuration object on the server (and return a JSON object) or create a JSON object on the client and populate it with data.
With these options and different implementations, you have a lot of choices. Which technique you choose depends on your coding standards and development needs. Building the JavaScript dynamically on the server allows you to build a single component that holds all the data you need. By using a StringBuilder (or a similar object), you can build a simple set of variables containing all the values from the configuration file. The downside to this approach is that you do not have the benefits Visual Studio (or your IDE) can offer. Tools like IntelliSense and IDE debugging would not be available, but the various tools integrated into the browser (like the Internet Explorer 10 Developer Tools or Firebug) might be all you need.
Using JavaScript components that communicate with a Web service is another approach to addressing the problem that client systems cannot retrieve data from a configuration file without some assistance from the server. This is the model that I’ll describe in this article. The negative side to this approach is complexity in design, but you build JavaScript as JavaScript and can leverage the power of your IDE. This approach also yields easier-to-read code for the next developer, which is an advantage for developers building software in a team environment.
Start with Security
As with any project, start by thinking about the risk to your application’s assets. In this case, assets are the configuration settings. Configuration data can be benign or sensitive information, depending on its use and purpose. You need to determine:
- Which configuration settings you need on the client?
- Why do you need them?
- What is the risk in exposing this data?
Remember, the client computer is a very hostile environment for your application. Once the data is on the client, it can be manipulated, abused and analyzed to mine any malicious information available from it. This raises another security mantra: Never store sensitive information on the client. This warning is usually made in reference to objects like ViewState and HiddenFields, but it applies equally to the configuration widget I’ll demonstrate.
The next security challenge is to keep settings deemed sensitive out of view for the configuration widget. You can do this by constraining the requests for configuration data to known safe data. For example, if you have a setting named “DocPath†that stores the full path to a folder on the server’s hard drive that’s used to save files uploaded to the application, you do not want that data leaking to the client. Having a full path to a folder that the Web application has write privileges to could be very harmful in the wrong hands. On the other hand, a setting like “DefaultAddress,†which provides only a default string address to use for transactions when an address is not provided, would not be a problem to expose.
Finally, you need to ensure that both the request and response are passed through constrain and sanitize processes. Requests from the calling component (such as a jQuery widget or JavaScript component) must be constrained to be a recognized, approved configuration setting. On the server, you need to validate that the request is approved (in other words, you do this twice—once on the client, and once on the server) and also reject any invalid input. After you retrieve the data from your configuration file, you can then sanitize the output to ensure that it does not contain any malicious content.
Design
Using the Project Silk Data Manager as a model, our Configuration Manager widget will provide access to a configuration file through client-side code and a server-side Web service. The data flow shown in Figure 1 depicts how a widget or JavaScript object will access the configuration file.
Figure 1 Data Flow for How a Widget or JavaScript Object Will Access a Configuration File
When a widget or JavaScript object calls for a configuration setting, the request is first passed through a constraining process to limit what the client can request from the services. Next, we check to see whether the configuration value is already populated, and if so, we return it to the caller. If it is not populated, we use the DataManager’s SendRequest method to connect to the configuration Web service. The service will sanitize our request to limit the amount of malicious content coming into the system and then retrieve the corresponding configuration value. This value is then validated to ensure that the configuration file has not been compromised and then passed to the proxy object and returned to the client. Once execution is back on the client, we stick the value into our cache object for later use. Finally, we return the data to the caller. Simple in design, this process has various checks of the data at each boundary by constraining what comes into the configuration widget, sanitizing what enters the service and validating what comes from the configuration file.
Development
Let’s begin with the Web service (the server component). For this article, I’ll build it in ASP.NET, but the concepts can be applied to any server-side development language. Start by creating a basic ASP.NET Web Forms project. You need to access a class in the service, so add an App_Code folder. For non-ASP.NET developers, App_Code folders store code objects that will compile and be available for the Web application. For this project, we need a Scripts folder, Services folder and a Default.aspx page. If you are building an ASP.NET 4.0 Web Forms project, your solution should look like Figure 2. Notice that all the files used in the application are in the location we will be referencing from.
Figure 2 The View of Our Web Service in Solution Explorer
Configuration Service
The first part of our Configuration Manager is the Configuration Service. This service is a server-side Web (or REST, WCF, etc.) service that uses two methods to provide configuration information. The first method returns the entire configuration as a single object, and the second method returns a single configuration name-value pair. For simplicity, create a class that will hold the name-value pairs for the configuration object and decorate it with the proper attributes to enable JSON serialization. Create a new class in your App_Code folder and call it ConstrainedConfiguration.cs. Add the code shown in Figure 3 to the file.
using System.Runtime.Serialization; namespace sj.models { [DataContract] public class ConstrainedConfiguration { #region Public Properties [DataMember] public string Name { get; set; } [DataMember] public string Value { get; set; } #endregion #region Constructor public ConstrainedConfiguration() { } #endregion } }
Figure 3 The ConstrainedConfiguration Class
Now let’s set up the configuration file to have a few settings we can consume. Add the following settings and values to your configuration file (web.config for ASP.NET):
<appSettings> <add key="Setting1" value="This is setting 1"/> <add key="Setting2" value="This is setting 2"/> <add key="Setting3" value="This is setting 3"/> <add key="Setting4" value="This is setting 4"/> <add key="Setting5" value="This is setting 5"/> <add key="Setting6" value="This is setting 6"/> <add key="clientSideApprovedSettings" value="Setting1,Setting2,Setting3"/> </appSettings>
The clientSideApprovedSettings setting is a listing of the setting keys that are available to the client through our Configuration Service.
With the class ready and the configuration file set up, you can create the service that will return a ConstrainedConfiguration back to the client. In the Services folder, create ConfigurationService.asmx, which will also create ConfigurationService.cs in the App_Code folder. This is the code-behind for the ConfigurationService Web service and the file in which we’ll write all our code.
Our Web service needs two methods, one that returns the entire approved configuration and another that returns a single setting. The first method is GetConfiguration, shown in Figure 4. It returns a collection of ConstrainedConfiguration objects to the client. Notice that the method is decorated with the ScriptMethod attribute, which sets the Response Format to JSON. This serializes and returns the object as JSON instead of as XML.
[WebMethod] [ScriptMethod(ResponseFormat=ResponseFormat.Json)] public sj.models.ConstrainedConfiguration[] GetConfiguration() { var settings = System.Configuration.ConfigurationManager.AppSettings; string[] approvedSettings = settings[_approvedSettingsName].Split(','); if (approvedSettings.Length > 0) { sj.models.ConstrainedConfiguration[] ccs = new sj.models.ConstrainedConfiguration[approvedSettings.Length]; for(int i = 0; i < approvedSettings.Length; i++) { sj.models.ConstrainedConfiguration cc = new sj.models.ConstrainedConfiguration(); cc.Name = Microsoft.Security.Application.Encoder.JavaScriptEncode(approvedSettings[i]); cc.Value = Microsoft.Security.Application.Encoder.JavaScriptEncode(settings[approvedSettings[i]]); ccs[i] = cc; } return ccs; } else throw new Exception("Approved settings must be supplied."); }
Figure 4 The GetConfiguration Method
First, we load the settings to access them without needing to specify the entire namespace each time. Next, we load in the clientSideApprovedSettings and split the values by the comma delimitating the values. To ensure that we do not assume values in the clientSideApprovedSettings, we check to be sure that the array of field names has values in it. If not, we throw an exception to be caught by the client- side code calling the service. If we have values, we instantiate the array of ConstrainedConfigurations, using the length of the approved fields as our new array’s length. We loop through each approved setting and create a ConstrainedConfiguration object storing the name and value from the application settings. Notice that I use the JavaScriptEncoder from the AntiXSS library to encode the settings. Doing this encodes the output from the AppSettings to limit the malicious code coming into the application. You might ask why I didn’t use HTML encoding. The answer is that the data won’t be used in an HTML context. We will be storing this data as a code object in JavaScript. When encoding, always be cognizant of how you will be using the data. Do not encode to a context that you are not using.
The second method, GetConfigurationSetting, shown in Figure 5, is very similar, but it limits what is retrieved to a single value.
[WebMethod] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public sj.models.ConstrainedConfiguration GetConfigurationSetting(int settingId) { var settings = System.Configuration.ConfigurationManager.AppSettings; string[] approvedFields = settings[_approvedSettingsName].Split(','); sj.models.ConstrainedConfiguration cc = new sj.models.ConstrainedConfiguration(); if (approvedFields.Contains(settings.Keys[settingId].ToLower())) { cc.Name = Microsoft.Security.Application.Encoder.JavaScriptEncode(settings.Keys[settingId]); cc.Value = Microsoft.Security.Application.Encoder.JavaScriptEncode(settings[settingId]); } return cc; }
Figure 5 GetConfigurationSetting
Configuration Manager
Our server-side components are ready. Now we need to build the client components. I used the DataManager and DataStore objects from Project Silk as the conceptual model for the ConfigurationManager and ConfigurationStore objects. The ConfigurationManager object is responsible for loading configuration information, and ConfigurationStore is the data store that holds the information. Let’s start with ConfigurationStore because it almost mirrors the DataStore from Project Silk.
You begin by declaring the object that will hold our configuration information:
_config: {},
This object will store the configuration settings retrieved through the ConfigurationManager. Next, you need two methods to get data from the _config object:
get: function (token) { return this._config[token]; }, getConfiguration: function () { return this._config; },
The get method returns a specific configuration setting, while the getConfiguration method returns the configuration collection. Along with the get methods are set methods:
set: function (token, value) { // Store the data this._config[token] = value; }, clear: function (token) { this._config[token] = undefined; }, clearAll: function () { this._config = {}; },
Finally, you need to add a hasData method that checks the number of objects in the _config object and returns a Boolean value indicating whether the length is greater than 0.
hasData: function () { if ({} != this._config) return true; else return false; }
The ConfigurationManager object populates the configuration object stored in ConfigurationStore. It has two methods: loadConfiguration and loadConfigurationSettting. The loadConfiguration method (see Figure 6) populates the entire configuration in ConfigurationStore. This method provides a callback parameter that can be used in an application that runs on a single page, like an HTML5 application.
loadConfiguration: function (callback) {
///Returns the populated configuration object
///
 callback to execute after the configuration has returned to the client
var that = this;
if (!sj.configurationStore.hasData()) {
sj.dataManager.sendRequest({
url: "/services/ConfigurationService.asmx/GetConfiguration",
type: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function (results) {
$.each(results.d, function (i, element) {
sj.configurationStore.set(element.Name, element.Value);
});
if (undefined !== callback)
callback(sj.configurationStore.getConfiguration());
},
error: function (ex) {
throw ex.responseText;
}
});
}
else {
if (undefined !== callback)
callback(sj.configurationStore.getConfiguration());
}
},
Figure 6 The loadConfiguration Method
First we stick this into a variable called that. This step is common practice in Project Silk and makes this (the widget) available for reference even when the meaning of this changes. As an example, if you need to reference a value of the widget in the error function of the Ajax call, you would not be able to use this to reference the widget. In the error function, this is the Ajax call not the widget. Even though we do not use that in later functions, we have it here for future implementation if necessary.
To leverage the configuration data already loaded to memory, we check sj.configurationStore.hasData() to see whether there is anything in the current configuration. Currently, hasData only checks to see whether the _config object is not an empty object, but you could enhance this method to include a check to match the expected parameter count, check each setting for a value, and so on. Use this code as a springboard for your own projects and customize it accordingly. If the configuration object is populated, we return the configuration object from the ConfigurationStore. If the configuration object is not populated, we populate it and execute the callback argument by passing in the configuration object. Using the callback argument allows you to do something with the configuration object after it is loaded.
Sometimes, you do not need all the configuration information. Sometimes, you just need a value or two for the page. Think of a multiple page interface (page as in separate ASPX files, not page like jQuery Mobile pages) where you do not need every configuration setting on each page. You could use the loadConfigurationSettting method to get only what you need when you need it, as shown in Figure 7.
loadConfigurationSetting: function (options) { var that = this; var currentOptions = $.extend({}, options, that._defaultLoadConfigSettingOptions); var _config = sj.configurationStore.getConfiguration() if (currentOptions.useCache && sj.configurationStore.hasData()) { var _hasSetting = false; for (var property in _config) { if (options.name == property) { if (undefined !== options.callback) options.callback({ "Name": property, "Value": _config[property] }); _hasSetting = true; } } if (_hasSetting) return; else { this._populateSetting(options); } } else { this._populateSetting(options); } },
Figure 7 The loadConfigurationSetting Method
Notice that currentOptions is created by using the $.extend jQuery function on the options provided to the method and the _defaultLoadConfigSettingOptions variable. This ensures that all the necessary option properties are provided. We then check to see whether the value is loaded in the ConfigurationStore. If it is, we return the value from that if useCache is set to true. Providing the useCache option allows you to force a refresh of the data if you want to. Configuration data probably does not change that often, so this is an unlikely need, but it is still available. If we try to pull from the existing ConfigurationStore and cannot find the value, we call the _populateSetting method (which is also called if the ConfigurationStore is empty or we set useCache to false). The populateSetting method, shown in Figure 8, is a very simple Ajax call that uses the callback provided.
_populateSetting: function (options) { sj.dataManager.sendRequest({ url: "/services/ConfigurationService.asmx/GetConfigurationSetting", type: "POST", dataType: "json", data: "{keyName:\"" + options.name + "\"}", contentType: "application/json; charset=utf-8", success: function (results) { sj.configurationStore.set(results.d.Name, results.d.Value); if (undefined !== options.callback) options.callback({ "Name": results.d.Name, "Value": results.d.Value }); }, error: function (ex) { throw ex.responseText; } }); }
Figure 8 The populateSetting Method
Pulling this all together, we are ready to create the sj.config.js script file for our project. A few samples of using this widget and complete code can be found at http://code.msdn.microsoft.com/ScriptJunkie-Mashup-Data-9687f4f9.
Configuration Widget for Approved Settings
In this article, I used the Project Silk design to set up a widget that pulls configuration data from the server-side of an application. Using this widget and a Web service, you can create a bridge between the client and the server, allowing HTML5 applications access to server components. While I used this methodology for accessing configuration data, you can adapt it to bring any server library to the client.
Think about other resources on the server that the client might need to access, such as file streams, server metadata, and so on. As you begin to experiment, always remember that you should never provide sensitive information to the client—validate the data coming from outside your application and restrict what can be requested to known approved data. By combining some risk management, the designs from Project Silk and a little creativity, you can build a JavaScript widget that brings application configuration data to the client for your HTML 5 apps to consume.
About the Author
For the past 10 years, Tim Kulp has been building Web applications using JavaScript, ASP.NET and C#. He leads the development team at FrontierMEDEX, building online medical and security intelligence tools, and is a CISSP and CEH specializing in secure software development.