Is there a way to get a web.config transformation in Visual Studio 2015+ to include the contents of an externally-referenced secrets file as described here?
This works well when developing locally, and doesn't include the file in source control, but when I go to deploy the web app to the server, I want it to include the username and password for another web service which it connects to. If I enter the settings manually in IIS on the server, they are lost each time I publish the app.
If I omit the keys in the appSettings block in my web.config (so that they are only referred to in the secrets file), the manually-entered settings in IIS on the server are removed completely whenever I publish the app.
Being able to refer to certain things in an external file for the sake of better security and not checking in passwords to source control isn't turning out to be a very good idea IMHO because my deployment is now a nightmare. I don't want to manually enter the passwords in the web.config file on the server after every deployment.
I'd rather not look into encryption, either, because I would have to do that for each server I deploy to so that the relevant machine key is used for each web.config file.
I've only recently thought about removing this password from source control, in response to a recent push to improve security practises at work - which I well understand and agree with - but I can also see why security is so poorly considered because the life of the ordinary developer becomes extremely unpleasant if there the tools available don't make it easy.
Surely there's a way without resorting to encryption?
Thanks.
Without a CI system I think your best option is probably a pre/post build action that executes a script?
I'd suggest you replace the actual values with tokens for your sensitive web.config values (something unique/easy to find like MY_PRODUCT_DATABASE_PASSWORD etc). Your web.config can then be checked into source control safely.
In VS you can add a build action to run a custom powershell or exe to basically perform a find-and-replace on the tokens with actual values before you zip & deploy as normal.
Exactly how/where you store the real values and how the script works is up to you. you could easily find a file on your deployment machine or a row in some database based on data passed to the script/exe from vs or from data within web.config itself (or embedded as a comment in web.config even).
Here's details of the variables available from vs you could pass to your exe or script in a build action: https://msdn.microsoft.com/en-us/library/42x5kfw4.aspx
It you wanted to use PowerShell you could read/replace/write values to a web.config(or any text file) like this answer: How can I replace every occurrence of a String in a file with PowerShell?
Following the Microsoft Docs example you've linked to, putting this XDT transformation inside Web.Release.config should do the trick:
<appSettings file="..\..\AppSettingsSecrets.config" xdt:Transform="SetAttributes">
<add key="mailAccount" xdt:Locator="Match(key)" xdt:Transform="Remove" />
<add key="mailPassword" xdt:Locator="Match(key)" xdt:Transform="Remove" />
<add key="TwilioSid" xdt:Locator="Match(key)" xdt:Transform="Remove" />
<add key="TwilioToken" xdt:Locator="Match(key)" xdt:Transform="Remove" />
<add key="TwilioFromPhone" xdt:Locator="Match(key)" xdt:Transform="Remove" />
<add key="GoogClientID" xdt:Locator="Match(key)" xdt:Transform="Remove" />
<add key="GoogClientSecret" xdt:Locator="Match(key)" xdt:Transform="Remove" />
</appSettings>
You dont need xdt:Locator attribute on <appSettings> element itself, because there is only one appSettings.
Edit: I've misunderstood the original question. The goal is to include contents of referenced file, which is not possible using XDT. There has to be another way.
Related
In asp.net we can include file attribute in appsettings that can contain secret values:
Web.config
<appSettings file="HiddenSettings.config">
</appSettings>
HiddenSettings.config
<?xml version="1.0"?>
<appSettings>
<add key="DbConnectionString" value="VALUE_IN_TEMPLATE_FILE" />
</appSettings>
In git, we can simply keep the HiddenSettings.config in git-ignore, so that sensitive information will not be checked-in.
The problem with this approach is every time the developer takes the first copy of the code, he/she needs to fill the secret data manually.
What's the best practice to do that automatically (i.e. no manual action to fill secret information in local/target deployment environment)?
I've been pondering that recently myself. The best method I could come up with so far is to link to a separate config file as you did and put that file to a separate private repository. Then add that repository as a submodule to the actual project. For instance:
git submodule add git#github.com:someUser/private-config.git
and the config points to the relative path of the submodule, something like:
<appSettings file="../../../../private-config/The.Real.Deal.config">
<add key="someKey" value="SAMPLE VALUE" />
</appSettings>
So when a user checks out the project (with initializing submodules) the config in the private-repo will be available. If not than they can download the project without the actual values and will only have access to the sample values.
Compared to other methods this looked good to me but I'm always open to suggestions too.
We are storing session data in the database, ideally each developer wants a session database on their own pcs, or possibly 1 developer might use inproc, another a mssql connection, another Oracle (if we get it working)
Is this possible? Can you access the session key in code and change it in the application startup? Or is there a file which could be merged with the web.config file that wouldn't get checked in?
Or option C which is easier but which I haven't thought of :-)
thanks
(Edit) Just found this which goes into this in some detail
developer specific app.config/web.config files in Visual Studio
(Answer). This came from a mix of Andrew Barber in the comments and the above
(1) In the web.config have this
<sessionState configSource="SystemWeb.config" />
(2) Make a file called SystemWebDefault.config which holds something like this:
<sessionState mode="SQLServer" allowCustomSqlDatabase="true" etc
(3) Each developer has to copy the default into a file called SystemWeb.config, changing it to suit themselves. This file should be explicitly ignored in subversion or whatever source control system you use.
(4) The build box needs a copy step
Something that might do what you want is NConfig (NuGet link). I've used it on projects to let multiple developers in our team have different connection strings for their local DB copies. It lets you define a machine specific config file to replace your default config values. For example you can define a {MachineName}.Custom.config that might look like this:
<?xml version="1.0"?>
<configuration>
<connectionStrings>
<add name="main" connectionString="data source=(local)\SQLExpress;uid=username;pwd=password;initial catalog=mydb" providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
I've never tried it with the session settings, but I can't find anything that says it won't work.
I want to create a generic web.config file for different web servers in VB.NET. So, depending on the server configuration requirements, applications can retrieve all values from that generic configuration file.
Is this possible? How would I do this?
This is just a random idea, it may not fit your needs though. You could create a configuration section for each server named by the name of the server. Create a helper class for reading configuration values that checks for any values in the section named after the server's name first, if it doesn't exist read it from a default configuration section.
I'm still not sure if this would be a wise decision, its just an option.
Technically, there's the machine.config which includes settings that apply to the entire machine.
web.config files can override some settings from it.
For everything that stays the same, use the a single web.config.
For everything that changes, use a reference to an external file.
<configuration>
<appSettings file="ExternalWeb.config">
<add key="MyKey" value="MyValue" />
</appSettings>
...
</configuration>
http://www.devx.com/vb2themax/Tip/18880
This way when things change in the main web.config, few things must be updated.
You may also consider using templates and code generation techniques to generate a web.config for each server.
How about a "mode" appsetting key/value. This "mode" can be set to "dev", "testing", "prod", etc. Then, set the mode of the current configuration file and prefix all the settings that would change with the mode.
Example:
<add key="mode" value="test" /> <!-- possible values: dev, test, prod -->
<add key="dev.dbconnstr" value="data source=DB;userid=ABC;password=DEF" />
<add key="test.dbconnstr" value="data source=DB;userid=ABC;password=DEF" />
<add key="prod.dbconnstr" value="data source=DB;userid=###;password=###" />
Then, use a configuration class to read the setting depending on the mode.
Example:
mode = ConfigurationManager.AppSettings("mode");
CongifurationManager.AppSettings(mode + ".dbconnstr");
Doing it this way, you can have the same config file deployed to all servers, and never have to worry about tweaking each server (except of course updating the "mode" value when deploying). I would also recommend not saving the production credentials in the other configuration files, instead replace it with a placeholder.
You could create a deployment script in something like nant which loads in a web.config containing placeholders for the configuration options. This could then replace the placeholders for the appropriate environments.
I'm entering a parallel test and dev stage where I need to use one db for test and a different one for dev. How can I have the app choose which connection string to implement based on which physical folder it (the app) sits in?
I know there are SVN strategies to consider but this is small-scale enough to avoid 2 sperate code-bases. Would like to be able to publish the same VS project to either of my 2 directories without having to remind myself to change the connection string.
I'm running under IIS7 so perhaps it offers better control than conditionals in (and overrides) web.config. (or not)
thankx!
A word of advice:
I wouldn't base your connection string on your published folder. Down the road, the folder might change, and folks may not be aware that that determines which connection string you're using.
Instead, control it with a setting in your web.config file. Just add a setting that allows you to switch between production and dev databases. In fact, you could simply test for the presence of a debug mode setting. If that setting is there, you're targeting the development database; otherwise, you're targeting production.
The nice thing about that solution is that it doesn't depend on where you deploy the site, and you can document the setting in the Web.config file.
Hope this helps.
Edit for Clarity: By "a debug mode setting" I mean a setting that determines which database you're targeting, dev/production. Not whether your application is running in Debug mode, since the Framework already provides a function that does that. Also, you wouldn't necessarily remove the setting, since you'd want to keep it for documentation purposes. Rather, you'd comment it out.
You could e.g. create a <connectionStrings> container that contains a connection string for each folder your app could be in:
<connectionStrings>
<add name="Folder1" connectionString=".....(conn str. #1)...:" />
<add name="Folder2" connectionString=".....(conn str. #2)...:" />
....
<add name="Folder-n" connectionString=".....(conn str. #n)...:" />
</connectionStrings>
and then just pick the right one, depending on where your app starts up from.
Marc
Get a unique string for the application, perhaps something like:
string folder = Regex.Match(Server.MapPath("~"), #"\(.+?)\$").Groups[0].Value;
Then use the string to get a value from the web.config:
ConnectionStringSetting connectionString = ConfigurationManager.ConnectionStrings["ConnectionString." + folder] ?? ConfigurationManager.ConnectionStrings["ConnectionString"];
In the web.config you can add several connection strings with the folder names appended for each dev site, and a default connection string with just the name and no folder.
I usually put the connection strings into a separate config file and reference them from the main web.config using configSource:
In web.config:
<?xml version="1.0"?>
<configuration>
<!-- connection strings are located in an external config
file to facilitate deployment in various environments -->
<connectionStrings configSource="connections.config"></connectionStrings>
</configuration>
In connections.config:
<?xml version="1.0"?>
<connectionStrings>
<add name="ConnectionName" connectionString="[your connection string]"/>
</connectionStrings>
Once deployed, I usually exclude connections.config from future deployments (unless it should be changed, that is).
On a project I'm working on we have a web application with three configuration files;
Web.Config
Web.Config.TestServer
Web.Config.LiveServer
When we release to the test server, Web.Config is renamed to Web.Config.Development and Web.Config.TestServer is renamed to Web.Config, making that configuration active for the application.
It is quite onerous keeping three very similar configuration files up to date, and this system is used across a number of applications which are part of this project; not just the website.
The differences in configuration are most commonly local directories or paths, URLs, IPs, port numbers, and email addresses.
I'm looking for a better way.
While your approach seems tedious, I find it to be the best approach.
I used to keep all of my configurations in a single web.config file, and simply had the "production" section commented out.
Shortly after this I had to do a "hybrid" test where my lookup data was coming from the production server, but the new data was being inserted into the test database. At that point I had to start piece-mealing what parts of the configuration block to comment/uncomment, and it became a nightmare.
Similarly, we have our server administrators do the actual migration from test to production, and most of them aren't fluent enough in .NET to know how to manage the web.config files. It is far easier for them to simply see a .test or .prod file and migrate the proper one up.
You could use something like a database to store all your configurations, but then you're running into another layer of abstraction and you have to manage that on top of things.
Once you get the knack or the template of how your two (or three) configuration files will be setup, it becomes a lot easier to manage them and you can have your test server configuration get modified for some unique testing without much hassle.
If you have a db server in the mix, you can create a table that has the config, the property name, and the property value in it, then all you have to do is change one value in the web.config, the config name (dev, test, prod).
If you have different dbs for each config, then the only thing that's different is the connection string.
Use Config Transformation and there is a blog here about it.
http://blogs.msdn.com/b/webdevtools/archive/2009/05/04/web-deployment-web-config-transformation.aspx
Basically you create targets named web.{build configuration}.config. In each target file you write your transformation where you can add, delete and modify nodes and attributes. Example could be
web.staging.configss
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<connectionStrings>
<add name="personalDB"
connectionString="Server=StagingBox; Database=personal; User Id=admin; password=StagingPersonalPassword"
providerName="System.Data.SqlClient" xdt:Transform="Replace" xdt:Locator="Match(name)" />
<add name="professionalDB"
connectionString="Server=StagingBox; Database=professional; User Id=professional; password=StagingProfessionalPassword"
providerName="System.Data.SqlClient" xdt:Transform="Replace" xdt:Locator="Match(name)"/>
</connectionStrings>
</configuration>
You then execute the transform by calling MSBuild {project file} /t:TransformWebConfig /p:Configuration=Staging