IIS using Microsoft.Web.Administration and commiting changes - asp.net

goal
Change IIS bindings and swap virtual directories on a per request basis. A simple application will open, replace and commit, using the Microsoft.Web.Administration. Following is a simplified flow:
code
ServerManager = New ServerManager()
config = ServerManager.GetApplicationHostConfiguration()
SiteList = config.GetSection("system.applicationHost/sites")
SitesCollection = SiteList.GetCollection()
_site = SitesCollection.FirstOrDefault(Function(f) f.GetAttributeValue("name").ToString() = "XXX")
_bindings = _site.GetCollection("bindings")
_bind As ConfigurationElement = _bindings.CreateElement("binding")
_bind("protocol") = "http"
_bind("bindingInformation") = String.Format("*:80:{0}", "www.zzz.yyy")
_bindings.Add(_bind)
ServerManager.CommitChanges()
problem
This code run on an administrator authenticated web page, and before the call, the thread is impersonated to make sure the privileges are in place. I'm allowed to read the .config but not to write! I also confirmed that, before any call, the thread is running as Administrator. I also tried using a LocalService pool but again, no luck.
the error
Filename: \\?\C:\Windows\system32\inetsrv\config\applicationHost.config
Error: Cannot write configuration file due to insufficient permissions
hresult: 0x80070005
at Microsoft.Web.Administration.Interop.AppHostWritableAdminManager.CommitChanges()

After some checking and experiment I find out that the way to make it work was
Do not use authentication on the application.
Do not use impersonation.
LocalService does not work.
Run it on a pool with enough privileges (administrator)
Looks like using impersonation breaks it again.
This is NOT the setting I wanted but I think I can survive and move the app to this unsecure pool, work the changes and the move it back to a generic ApplicationPoolIdentity.

Related

Application insights: no data for dependency calls

ASP.NET site hosted on Azure VM. ApplicationInsights Status Monitor installed on VM. Default ApplicationInsights.config created by "Add AppInsights" menu in Visual Studio, only custom initializer added instead of ikey:
<Add Type="WebSite.WebSiteTelemetryInitializer, WebSite" />
Code:
public class WebSiteTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
TelemetryConfiguration.Active.InstrumentationKey = WebConfigurationManager.AppSettings["ikey"];
telemetry.Context.User.Id = Environment.UserName;
telemetry.Context.Session.Id = Guid.NewGuid().ToString();
telemetry.Context.Component.Version = typeof(WebSiteTelemetryInitializer).Assembly.GetName().Version.ToString();
}
}
All works as expected, but there is no performance data (Cpu, memory). After adding apppool user to Performance Monitor Users group:
$group = [ADSI]"WinNT://$Env:ComputerName/Performance Monitor Users,group"
$ntAccount = New-Object System.Security.Principal.NTAccount("IIS APPPOOL\DefaultAppPool")
$strSID = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
$user = [ADSI]"WinNT://$strSID"
$group.Add($user.Path)
there is no data for dependency calls.
UPDATE
There are 3 repeating trace logs:
AI (Internal): Complete creating shadow copy of extension,
extensionBaseDirectory: C:\inetpub\wwwroot\site\bin, extensionName:
Microsoft.ApplicationInsights.Extensions.Intercept with error System.UnauthorizedAccessException: Access to the path 'C:\Windows\system32\config\systemprofile' is denied.
AI (Internal): Extension attach failure, unable to attach, baseFolder: C:\inetpub\wwwroot\site\bin, nativeExtensionName: Microsoft.ApplicationInsights.Extensions.Intercept
AI (Internal): [msg=RemoteDependencyModule failed];[msg=System.InvalidOperationException: Failed to attach extension, hresult: 2147500037
Remote dependencies:
Ok, so that is what is preventing ApplicationInsights from collecting dependencies:
AI (Internal): Complete creating shadow copy of extension, extensionBaseDirectory: C:\inetpub\wwwroot\site\bin, extensionName: Microsoft.ApplicationInsights.Extensions.Intercept with error System.UnauthorizedAccessException: Access to the path 'C:\Windows\system32\config\systemprofile' is denied.
C:\Windows\system32\config\systemprofile is what is set as a temp folder for your process. You need to change temp folder for the process and make sure that your application can write there. (ApplicationInsights is coping native binaries there that are used by profiler. Temp folder is also used when you have temporary internet access issues. It saves events that were not sent and sends them when connection is restored.)
Performance counters:
In order to collect performance counters the user that application pool runs under (usually it's ApplicationPoolIdentity) should be a member of Performance Monitor Users group on the box. Ensure that it's added there and you should do iisreset after adding the user to the group otherwise changes will not take effect.
Described here at the bottom:
http://blogs.msdn.com/b/visualstudioalm/archive/2014/12/11/updated-application-insights-status-monitor-to-support-12-and-later-application-insights-sdk.aspx
Just to add, you can change the temp folder that Application Insights writes to by editing the end of applicationsinsights.config file and adding the location of temp folder. Here's what I've done:
<TelemetryChannel Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel, Microsoft.AI.ServerTelemetryChannel">
<StorageFolder>D:\AITempFolder</StorageFolder>
Hope this helps someone else too.

Active directory - exception has been thrown by the target of an invocation

I have a web application in a separate server than Active Directory and I want to change a user password. The code is the next:
string newPassword = Membership.GeneratePassword(int.Parse(WebConfigurationManager.AppSettings["passLenght"]),
int.Parse(WebConfigurationManager.AppSettings["passNonAlpha"]));
DirectoryEntry de = new DirectoryEntry(WebConfigurationManager.ConnectionStrings["ADConnString"].ConnectionString,
WebConfigurationManager.AppSettings["ADAdmin"], WebConfigurationManager.AppSettings["ADAdminPass"]);
DirectorySearcher deSearch = new DirectorySearcher(de);
deSearch.Filter = "(&(objectClass=user) (userPrincipalName=" + name + "))";
SearchResultCollection results = deSearch.FindAll();
if (results.Count == 1)
{
foreach (SearchResult OneSearchResult in results)
{
DirectoryEntry AlterUser = OneSearchResult.GetDirectoryEntry();
AlterUser.AuthenticationType = AuthenticationTypes.Secure;
AlterUser.Invoke("SetPassword", newPassword);
AlterUser.CommitChanges();
AlterUser.Close();
}
}
When I run this in my development environment (where Active Directory and the web application are on the same server) it is working. But when I try to run it in the production environment I am having the next error:
Exception has been thrown by the target of an invocation
What am I missing?
Thanks.
EDIT:
I could go deep in the exception error and I get this:
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
Permissions are the issue. The account under which your ASP.NET code is running doesn't have the permission to set the account password.
Either:
Run the AppPool under a user that has the required permissions, or
Use impersonation to elevate the permissions for the SetPassword call
The reason it is working in your dev environment/failing in production is likely due to a combination of:
You are running the app under the Visual Studio development web server that runs under your user account, which has the necessary permissions. Running it under "real" IIS will run it under a less privileged account.
In the live environment there's another machine hop from the web server to the AD server, and the credentials don't get passed along. The web server needs to have network credentials (either as part of the AppPool identity, or a call to LogonUser) in order to authenticate to AD.
The code looks correct. This could be happening because the password your sending though Active Directory does not meet the minimum requirements. Trying using a more complex password such as "M2k3ThisWork!"
If you want to change the password of AD then you use this
AlterUser.Invoke("ChangePassword", OldPassword, newPassword);

How to use SharpSVN in ASP.NET?

Trying to use use SharpSVN in an ASP.NET app. So far, it's been nothing but trouble. First, I kept getting permission errors on "lock" files (that don't exist), even though NETWORK SERVICE has full permissions on the directories. Finally in frustration I just granted Everyone full control. Now I get a new error:
OPTIONS of 'https://server/svn/repo': authorization failed: Could not authenticate to server: rejected Basic challenge (https://server)
This happens whether I have the DefaultCredentials set below or not:
using (SvnClient client = new SvnClient())
{
//client.Authentication.DefaultCredentials = new System.Net.NetworkCredential("user", "password");
client.LoadConfiguration(#"C:\users\myuser\AppData\Roaming\Subversion");
SvnUpdateResult result;
client.Update(workingdir, out result);
}
Any clues? I wish there was SOME documentation with this library, as it seems so useful.
The user you need to grant permission is most likely the ASPNET user, as that's the user the ASP.NET code runs as by default.
ASPNET user is a local account, preferably youd'd want to run this code in an Impersonate block, using a network account set up for this specific reason

Access Denied errors accessing IIS WMI provider from ASP

I have a Windows 2003 server running IIS 6 and have some scripts that do automated setup and creation of websites. They are not working on a new server I cam commissioning (they already work happily on 3 other W2K3 servers). The problem appear to boil down to WMI security on the IIS provider. The ASP code below represents the problem (although it is not the original code that causes the problem - this is a simplified demonstration of the problem).
Set wmiProvider = GetObject("winmgmts:\\.\root\MicrosoftIISv2")
If wmiProvider is Nothing Then
Response.Write "Failed to get WMI provider MicrosoftIISv2<br>"
End If
Response.Write "Querying for IISWebService...<br>"
Set colItems = wmiProvider.ExecQuery("Select * From IISWebServer",,0)
Response.Write "Error: " & Hex(Err.Number) & " (" & Err.Description & ")<br>"
If I run this in my browser, I get an access denied error reported after the ExecQuery call. I have set WMI access for the IUSR_ user from the Root branch all the way down. In fact, I can query for IP address information using the CIMV2 provider quite happily. If I put the IUSR user in the machine admins group it all works, but I don't really want to do that.
This must be a DCOM/WMI security problem, but I can't work out what else there is. Can anyone shed any light?
After reading G. Stoynev's comment asking if any events were logged in the Windows Logs, I checked the event logs on the server to which I'm attempting to access IIS remotely via WMI, and lo and behold I found an event with the following text:
Access to the root\WebAdministration namespace was denied because the namespace is marked with RequiresEncryption but the script or application attempted to connect to this namespace with an authentication level below Pkt_Privacy. Change the authentication level to Pkt_Privacy and run the script or application again.
See the code in this answer to the related SO question c# - "Access is denied" Exception with WMI.
Here's some example C# code that I added that seemed to resolve this issue for me:
ConnectionOptions options = new ConnectionOptions();
options.Authentication = AuthenticationLevel.PacketPrivacy;
ManagementScope managementScope = new ManagementScope(#"\\remote-server\root\WebAdministration", options);
// ...
If this is something that you intend to run as a tool for yourself or your admin (as opposed to the unwashed anonymous masses), here is a way I have used in the past (YMMV):
Set up a new directory in your website (e.g. /SiteCreate) and place your WMI scripts there
Configure a Windows user that has appropriate rights (probably admin in this case but you should use whatever is pertinent to your app)
Turn off the anonymous access to the directory you created in step 1 and then set the security to allow access only to the user you created in step 2 (turn on the authentication for that directory)
Now, when you navigate to that directory in your browser, you should get a login prompt. When you enter the username/password you created in step 2 your script will have the appropriate rights to perform your WMI requests.
Not a DCOM issue, more so a WMI security and encryption issue. Try changing the GetObject moniker to include impersonation and pktPrivacy, eg:
Set wmiProvider = GetObject("winmgmts:{impersonationLevel=impersonate;authenticationLevel=pktPrivacy}!\root\MicrosoftIISv2")
Refer to the follow MS article for more info:
http://msdn.microsoft.com/en-us/library/aa393618(v=vs.85).aspx

How do I access database via virtual folder which points at a remote share

I'm having a problem getting access to a database which lives on a remote server.
I have a ASP.NET 2.0 webpage that is trying to connect to a database.
The database is accessed via a virtual folder (which I set up in IIS).
The virtual folder points at a remote share which contains the database.
The virtual folder (in the web apps root directory) is pointing at a share on a remote server via a UNC path:
\\databaseServerName\databaseFolder$\
The virtual folder has 'read' and 'browse' permissions set to 'true'.
I store the connection string in the 'appSettings' section of the web.config:
<add key="conStrVirtual" value="Provider=Microsoft.Jet.OleDb.4.0;Data Source=http://webAppServerName/virtualFolderName/databaseName.MDB;Jet OLEDB:Database Password=dumbPassword;"/>
The connection object is declard on my .aspx page:
Dim objConnVirtual As New OleDbConnection(ConfigurationManager.AppSettings("conStrVirtual"))
Here is the code that tries to use the connection object:
Public Sub Test()
If objConnVirtual.State <> ConnectionState.Open Then
objConnVirtual.Open()
End If
Dim cmd As OleDbCommand = New OleDbCommand("SELECT * FROM TableName", objConnVirtual)
objDR = cmd.ExecuteReader()
If objDR.Read() Then
response.write("Shazaam! Data shows up here")
End If
objDR.Close()
objConnVirtual.Close()
End Sub
When I run the above code I get the following error (on this line of the code 'objConnVirtual.Open()':
Exception Details: System.Data.OleDb.OleDbException: Not a valid file name.
I have checked the database name and it is correct (even copy/pasted it to make sure)
If I put the 'Data Source' section of the connection string into the address bar of my browser I can successfully see the contents of the share on the remote server.
Not sure if this is a problem with permissions or with the code.
I have googled the crap out of this but have not been able to find a solution.
Any help is much appreciated.
When accessing a remote Access MDB database, you have to specify a UNC path like \\remoteMachine\Share\test.mdb.
Make sure your application pool identity has the right permissions to connect to the remote share. By default on IIS 6 you are working with the Network Service account, which is by default not allowed to access a remote share.
The best way is to let the AppPool run with a dedicated service user.
What is the account being used on your server when your web app tries to read the db file? Whatever this user account is, it needs to have permissions to read that folder/file. In IIS6 you can configure the virtual folder to use any user account... on the Directory Security tab there's an Edit button under Authentication and access control.
It seems likely that your error message is just a generic error message, and the permissions problem is your real issue.
make sure the two servers have internal access to each other and also specify the ip & port of db server in your connection string .
Update
I should also mention that it works on my machine (but not once loaded up to the production box) if I declare the connection string in the 'appSettings' section of the web.config like this:
<add key="conStrVirtual" value="Provider=Microsoft.Jet.OleDb.4.0;Data Source=\\databaseServerName\databaseFolder$\databaseName.MDB;Jet OLEDB:Database Password=dumbPassword;"/>
This leads me to think that it could be an issue with needing to use domain credentials other than the local IUSER account.
UPDATE
First up, thank you to everyone who submitted answers.
However, we ended up not using the 'connect to remote database via virtual folder' method because the complexity of the permissions needed to get this to work was causing us more problems than it was worth. We put the UNC path back into the connection string, which may not be the best way to do this, but is working for us.

Resources