Write files to network drive from IIS using Managed Service Account - c#

I have an ASP.NET MVC 5 app hosted in IIS 10 on Windows 2016. Our sys admins have created a Managed Service Account (MSA) that is tied to this server and has read/write permissions to a folder on the network. I need to write PDFs to that folder from the web application using the MSA.
Currently, I'm simply trying to write a simple text file to the folder:
System.IO.File.WriteAllText(#"\\SomeUncPath\Reports\test.txt", "sample text");
The above produces this error, which is to be expected,
System.UnauthorizedAccessException: Access to the path '\SomeUncPath\Reports\test.txt' is denied.
I followed this video: https://www.youtube.com/watch?v=lBv81lwZgIo to no avail. It just caused the site to generate a 503 error.
Is it possible to write the files using C# impersonation, such as described in this article? But how do you impersonate an MSA, which has a password set by the system?
I tried the following code using the SimpleImpersonation:
var cred = new UserCredentials("myDomain", "someMsa$", "");
Impersonation.RunAsUser(cred, LogonType.Batch, () =>
{
System.IO.File.WriteAllText(#"\\SomeUncPath\Reports", "sample text");
}
);
The above throws this:
System.ArgumentException: Password cannot be empty or consist solely of whitespace characters. Parameter name: password
Update 1: The server is throwing the following error into the System log:
Application pool SomePool has been disabled. Windows Process Activation Service (WAS) encountered a failure when it started a worker process to serve the application pool.
And these two warnings:
Application pool SomePool has been disabled. Windows Process Activation Service (WAS) did not create a worker process to serve the application pool because the application pool identity is invalid.
and
The identity of application pool SomePool is invalid. The user name or password that is specified for the identity may be incorrect, or the user may not have batch logon rights. If the identity is not corrected, the application pool will be disabled when the application pool receives its first request. If batch logon rights are causing the problem, the identity in the IIS configuration store must be changed after rights have been granted before Windows Process Activation Service (WAS) can retry the logon. If the identity remains invalid after the first request for the application pool is processed, the application pool will be disabled. The data field contains the error number.
I tried this and rebooted the server but the issue persists.
Update 2: If I give the app pool my credentials, the app loads without any issues. It's only on the MSA that it fails with the above error/warnings. What could be wrong with the MSA?
Update 3: The issue was how I was adding the MSA to the app pool. I needed to include my domain in the username: myDomain\someMsa$. Once I had that in, it worked like a charm!

The issue had to do with missing the domain when setting the MSA as the app pool identity. When adding it, I needed to set it as myDomain\someMsa$ instead of simply someMsa$. What's strange is how IIS didn't give an error, perhaps because the MSA account was considered both a local and domain account.
Also, in our case, we didn't need the "Log on as a batch" permission for the MSA. It worked fine without it.

Related

Impersonation works with local shared file, but does not work with remote one

I have a .net/c# web app (web api) with windows authentication. The service is hosted on my local computer, IIS 10. Application pool identity set to me, currently logged in windows user. Computer is in active directory domain.
I want to access shared file using account, currently logged in to the app. File has appropriate permissions. For this purposes I use impersonation like this:
if (HttpContext.Current.User.Identity is WindowsIdentity windowsIdentity)
{
using (windowsIdentity.Impersonate())
{
FileStream stream = new FileStream(#"\\server\share\file.ext", FileMode.Open, FileAccess.Read);
}
}
I logging in with current windows account, the same as set in app pool identity. This works fine with a shared file on a local computer, where the app is hosted. But does not work with a remote shared file, located on another computer. The other computer is in active directory domain too.
From a hosting computer I can access shared file using windows explorer or my browser. Also if I do not impersonate user, .net trying to access shared file with application pool identity account(set to the same user, me) and it succeeded for both, local and remote files.
It also works with impersonated identity got from LogonUser method from advapi32.dll. But it requires user password and I do not want to request password from user, already logged in to app.
What am i doing wrong?
Update: If a shared file located on hosting machine, then logon event generated by windows (security tab in event viewer) shows the right user. If a shared file located on another machine, then logon event generated by windows on this machine shows the anonymous user. So, account somehow lost.
Update 2: Impersonation works if I run site on IIS like localhost(localhost in url). But if I run it using ip or site name it stops working.
Update 3: Wireshark shows the request for getting ticket(to access shared file server) for delegation fails with error "KRB5KDC_ERR_BADOPTION NT Status: STATUS_NOT_FOUND". Delegation for application pool user allowed in AD.
The same ticket(for cifs/fileshareservername) without delegation can be successfully retrieved(wireshark shows) when doing Dir command in cmd. Seems like problem in AD.
Can't for sure if what you're doing is wrong, but I can tell you what I've done to do a very similar thing. My .Net site doesn't have WindowsLogin normally, so I had to make an extra jump that I think you could do to facilitate the same thing, just perhaps not the best answer.
At login ( in my membershipProvider ) I run this code:
try
{
if (LogonUser(user,domain,password, [AD_LOGIN],
LOGON32_PROVIDER_DEFAULT, ref handle))
{
IntPtr tokenDuplicate = IntPtr.Zero;
if (DuplicateToken(handle, SecurityImpersonation,
ref tokenDuplicate) != 0)
{
// store off duplicate token here
}
}
}
finally
{
if (handle != IntPtr.Zero)
{
CloseHandle(handle);
}
}
then when you need to impersonate, do this:
var context = WindowsIdentity.Impersonate(tokenDuplicate);
try
{
// do your file access here
}
finally
{
context.Dispose();
}
I had to do some funny conversion of that tokenDuplicate variable. It's an integer value but pointing at a specific memory address where the token information is stored. It stays good as long as your logged in.
Why you can't do the impersonate directly on your identity, don't know. I just know it worked for me with a token, and that was my method to get a token I could use for impersonation.
It started working for me with the following settings.
IIS:
Application pool identity set to a specific user(let's say IISUser).
Windows authentication enabled for IIS site. Kernel mode enabled (important!).
All other magic is happening in Active directory:
Computer with shared files has an SPN: cifs/%computer_name%.
Hosting computer(where IIS installed) is trusted for delegation. Delegation tab -> Trust this computer for delegation to specified services only -> Use any authentication protocol. Then select SPN from item 1. Important: you should select computer SPN, not IISUser SPN.
IISUser is trusted for delegation for SPN from item 1.

how to use Microsoft.Web.Administration properly to start an application pool from a web page?

I was tasked to find a way to restart an application pool from a web page. The IIS is hosted by our own IT department, so any security settings needed will be changed if necessary.
I am using Microsoft.Web.Administration.dll and trying this for my local IIS (7.5):
ServerManager serverManager = new ServerManager();
ApplicationPoolCollection applicationPoolCollection = serverManager.ApplicationPools;
foreach (ApplicationPool applicationPool in applicationPoolCollection)
{
appPoolNames.Add(applicationPool.Name);
// If the applicationPool is stopped, restart it.
if (applicationPool.State == ObjectState.Stopped)
{
applicationPool.Start();
}
}
// CommitChanges to persist the changes to the ApplicationHost.config.
serverManager.CommitChanges();
If I put this logic in a winform and run as local admin, my app pool in local IIS will be started. However, if I put the same logic in a webpage, it fails with error at this line with error:
if (applicationPool.State == ObjectState.Stopped)
Exception: System.ComponentModel.Win32Exception: Access is denied
I think this is due to security, but I already give a local admin user to my app pool. Is there a way to achieve this in a web page?
I assume the web page is not in the same application pool as the one you are trying to restart, which would not be a good idea (because the web page would suddenly lose its process host). So assuming it is a different application pool, then it is the identity of the web page's application pool which matters - this will need to have permissions to restart the target application pool.

Accessing Network Path in Windows Service C#

I have developed a windows service using local system as Account. I have used
network path of file
FileInfo fi = new FileInfo(#"\\epm-server\penDocuments_LabMeds\" + Convert.ToString(dr["mrn"]) + "\\SyncedXML\\" + Convert.ToString(dr["xmlfile"]));
if (!fi.Exists)
boolFileNotFound = true;
A dynamic path of a file that is built from database.
It works fine when I run Windows Service in Debug Mode, but when I install it then fileNotExists returns TRUE always like the file doesnt exist but infact it does exist.
This is bugging me a lot now. Kindly help me why its not working. Its a server path. Its getting opened in my PC.
Thanks
Did you notice the double backslashes in front and after SyncedXML (\\SyncedXML\\)?
This is probably the cause of your error.
Additionally I'd use string.Format in such cases to reduce the inadvertently addition of unwanted characters:
var path = string.Format(#"\\epm-server\penDocuments_LabMeds\{0}\SyncedXML\{1}", dr[mrn], dr[xmlfile]);
var fi = new FileInfo(path);
Edit:
If it's permission-related, then it's very likely that your local system account (in whose context the service is running) isn't allowed to access the epm-server.
The path is accessible if you're opening it directly or if you're running the service in debug mode as this is happening in your user context (e.g. YOURDOMAIN\vickyshazad), and you're allowed to access the ressource, whereas NT AUTHORITY\SYSTEM is not.
It's usually a good practise to have a special service account for your developed windows service and grant this user only and exactly the required permissions (least privilege). Maybe ask your system administrator for a service user.
Local System (NT AUTHORITY\SYSTEM) is a highly privileged account that's not recommended to use in general (see MSDN).
Most services do not need such a high privilege level. If your service does not need these privileges, and it is not an interactive service, consider using the LocalService account or the NetworkService account.

Access Denied on a Shared Folder from MVC Web Application

I have an MVC Web application that generates Excel and PDF reports (using Crystal) using templates .xlt and .rpt, it generates the reports without a glitch when I place the templates in the web server itself but once I place the templates in a remote location then I get an Access Denied error which I found out through process monitor, screen shot below
When I manually browse the remote folder through explorer from the server its all OK and I can open the files I needed its just fires the access denied error when its the server reading the files. My web application is using the ApplicationPoolIdentity in Integrated Pipeline. Authentication is through impersonation and Windows Authentication. Whats even makes it confusing is that the User who runs the Excel templates is my self but I get the access denied, while user used in generating PDF is IIS Apppool.
Does anyone know how to resolve the access denied issue, I already tried putting all users full access on that folder but still it does not work.
ADDITIONAL INFO
I am using IIS 7.5, I also checked on the File Server where the share is, on the Event Logs the user registered is not me but with the following details
An account was successfully logged on.
Subject:
Security ID: NULL SID
Account Name: -
Account Domain: -
Logon ID: 0x0
Logon Type: 3
New Logon:
Security ID: ANONYMOUS LOGON
Account Name: ANONYMOUS LOGON
Account Domain: NT AUTHORITY
Logon ID: 0x90eb7c7
Logon GUID: {00000000-0000-0000-0000-000000000000}
Process Information:
Process ID: 0x0
Process Name: -
Network Information:
Workstation Name: MYWEBSERVER
Source Network Address: 10.10.10.01
Source Port: 00000
Detailed Authentication Information:
Logon Process: NtLmSsp
Authentication Package: NTLM
Transited Services: -
Package Name (NTLM only): NTLM V1
Key Length: 128
UPDATE
I need to use ApplicationPoolIdentity in this instance, so I am looking for a solution that can still use ApplicationPoolIdentity.
ANOTHER UPDATE
I tried #Davids suggestion below and now I get same error message
System.Runtime.InteropServices.COMException (0x800A03EC): Microsoft
Excel cannot access the file '\MyServer\Templates\MyTemplate.xlt'.
There are several possible reasons:
• The file name or path does not exist. • The file is being used by
another program. • The workbook you are trying to save has the same
name as a currently open workbook. at
Microsoft.Office.Interop.Excel.Workbooks.Open(String Filename, Object
UpdateLinks, Object ReadOnly, Object Format, Object Password, Object
WriteResPassword, Object IgnoreReadOnlyRecommended, Object Origin,
Object Delimiter, Object Editable, Object Notify, Object Converter,
Object AddToMru, Object Local, Object CorruptLoad) at
Ci.Infrastructure.Reporting.ReportProviderExcel.RunReport()
I believe your problem is because the application pool needs to be configured to run either as a domain account or network service account.
If you choose the latter you'll need to grant permissions to '<domainname>\<machinename>$' if you choose to run as a specific account then this is the user you will need to grant permissions to.
You've already granted everyone access, so it should just be a case of changing the app pool user, but once you have it working I recommend you restrict this to the specific account.
The following link will give you more information:
http://www.iis.net/learn/manage/configuring-security/application-pool-identities

File.GetAttributes(unc) generating "Network path not found" error

I'm passing a UNC path to File.GetAttributes(). This works fine when running off my local, but when I move the site to the test server, I get a "Network path not found" error. I am able to navigate to the path from the test server, so I don't know why I would be getting this error. The code is very simple. This is where it errors out:
try
{
if (FileAttributes.Directory != (FileAttributes.Directory & File.GetAttributes(directory)))
directory = GetPath(directory);
}
catch...
Being able to navigate to the share from the server doesn't mean much - remember your application is running under another account, usually whatever the app pool is set to. That account normally does not have access to anything other than the resources in the local machine, because it's not a domain account.
Check what account the app pool is running under. You might have to change that to a domain account on your AD forest to be able to access things on other servers.
Most likely it is "NTLM one hop" issue - credentials of a remote user can't be passed to thrird server.
Machine 1:Browser -(credentials)-> Machine 2:ASP.Net site -(no credentials)-> Machine 3.
Solution is to access "machine 3" under known (i.e. process) account or use Kerberos.

Categories

Resources