We have a WCF service hosted in IIS running under Default AppPool under 'Network Service' account. At some point in the service, there is a need to read a file from a location to which Network Service account does not have access and thus we need to use an 'Administrator' account to load that file in memory.
I have tried impersonation to accomplish this and my code does not throw any exception for the Administrator credentials I pass in and I am sure the Credentials are right. But the WindowsIdentity.GetCurrent().Name still shows NT Authority\Network Service even after the call to impersonate. I am using the LogonUser Win32 call that is used by many people based on my searches. I do not have any impersonation settings in my web.config from what I can tell.
My question is whether WindowsIdentity.GetCurrent().Name is the correct way to check credentials under which the code is running at that moment. If that is true, then what am I doing wrong here.
I have tried two different ways to impersonate:
https://github.com/mj1856/SimpleImpersonation which is basically a wrapper around the win32 LogonUser method
I have also tried to add the code natively as below:
/** WindowsIdentity.GetCurrent().Name prints 'NT Authority\Network Service' **/
SafeTokenHandle safeTokenHandle;
const int logon32ProviderDefault = 0;
const int logon32LogonNewCredentials = 9; // this logon type is required to allow cross-domain (WORKGROUP -> ourDomain) authentication
// Call LogonUser to obtain a handle to an access token.
bool logonSucceeded = LogonUser("user", "domain", "password", logon32LogonNewCredentials, logon32ProviderDefault, out safeTokenHandle);
if (!logonSucceeded)
{
int ret = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(ret);
}
using (safeTokenHandle)
{
WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
/** At this point, WindowsIdentity.GetCurrent().Name STILL says NT Authority\Network Service. Shouldn't it say domain\username passed in the call to LogonUser above**/
}
}
In both of these ways, I do not get an exception for credentials, so it seems that impersonation succeeds, but it seems its not really changing the credentials to the impersonated user.
Related
We have a web app that runs on our corporate intranet. I get the following error when I try to access a network path in my MVC controller:
Access to the path '\Server001\SharedFiles\CA' is denied.
The App is hosted on IIS and app pool is set to ApplicationPoolIdentity. I do not want to set it with an account that has access right to all the directories. I'd like each user to only access the folders they're allowed to.
After some research I figured the only way is to programmatically impersonate the user when it's needed. To test this out, I created a New ASP.NET Core 6 MVC project with Authentication set to Windows(Also enabled Windows Authentication in IIS site). Then I added the following code to my controller:
public async Task<IActionResult> Index()
{
// The user used as Log On as for the Windows Service
var serviceUser = WindowsIdentity.GetCurrent();
// returns "IIS APPPOOL\MvcTest"
// The user to be impersonated
// COMPANYDOMAIN\MyName
var userToImpersonate = (WindowsIdentity)HttpContext.User.Identity;
await WindowsIdentity.RunImpersonatedAsync(userToImpersonate.AccessToken, async () =>
{
var ImpersonatedUser = WindowsIdentity.GetCurrent();
_logger.LogInformation(ImpersonatedUser.ImpersonationLevel.ToString());
// returns "impersonate"
_logger.LogInformation(ImpersonatedUser.Name);
// Here we are getting "COMPANYDOMAIN\MyName"
try
{
var files = Directory.GetFiles("\\\\Server001\\SharedFiles\\CA");
return View(files);
}
catch (Exception ex) { }
});
}
Although this shows that WindowsIdentity.GetCurrent().Name has changed to my domain account(what I logged in as) but for some reason it is not accepting the impersonated user. I still get access denied error. Is this permission issues?
I am able to browse the "\Server001\SharedFiles" using my domain account(COMPANYDOMAIN\MyName), Also when I change the app pool identity to my domain account, the app still works.
If WindowsIdentity.GetCurrent().Name shows the correct user, then your impersonation is likely working correctly.
File shares in Windows have two sets of permissions that define what a user can do:
The share permissions. This defines who is allowed to access the folder remotely. These are set in the same place where you initially setup the share.
File system permissions. These are the normal file permissions that you would think of.
Make sure that both the share permissions and the file system permissions allow the user access. It is possible for the file system permissions to allow someone full control, but the share permissions to deny them any access. In that case they could access and modify any files locally, but couldn't do anything through the share.
I would usually set the share permissions to Read/Write for Everyone and then use the file system permissions to restrict access.
I've created a Sonos music service and added it to my speaker with customsd. Both endpoint urls (regular and secure) are available. The service is successfully added to the speaker.
With SoapUI I can send a https request for GetSessionId and it returns a valid response.
I use sessionId authentication, so when I want to add my account to the service in Sonos, the service asks for a username and a password. After filling in, I get a connection error. Problem with adding account. Connection can not be made.
The problem is, I expect a 'GetSessionId' request entering my music service (So I can debug the request), but it seems nothing comes in.
Can anyone tell me why or what is happening?
Are you setting the authentication type to Session ID when you're adding the service from customsd?
Here is an example of how the function should look like (in PHP):
public function getSessionId($args)
{
$user = $args->username;
$pass = $args->password;
//Check the user and pass in your service
//if login successful, set $sessionId to the session ID of the user
//else if login unsuccessful, throw new SoapFault('Client.LoginInvalid','Client.LoginInvalid');
return array('getSessionIdResult' => $sessionId);
}
I've created a WCF webservice, that can dynamically call other webservices/db connections with DLL's loaded with .LoadFile('from assembly'). Inside one of these assemblies, another webservice is called dynamically with a passed in network credential as follows:
WebClient client = new WebClient();
client.Credentials = this.networkCredential; //This credential is passed in
RequestStream requestStream = client.OpenRead(this.url);
//rest of code. The .OpenRead is giving 401 error (not authorized).
When I do this in debug mode from a test console application and creating the network credentials as follows:
NetworkCredential networkCredential = new NetworkCredential(<userid>,<password>,<domain>);
this works fine.
The failing code is providing networkcredentials as follows
System.Net.NetworkCredential networkCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
Any help would be greatly appreciated.
Unless you are using impersonation, DefaultNetworkCredentials attempts to use the credentials of the process running your ASP.NET website, not the credentials of the user accessing your site. In order to use the credentials of the user, you should wrap your call to the web services in an impersonation context.
var impersonationContext = HttpContext.Current.Request.User.Identity.Impersonate();
if(impersonationContext != null)
{
//call your webservice here. DefaultNetworkCredentials will be what you expect.
impersonationContext.Undo();
}
There are, of course, conditions. The user must be authenticated (can't be an anonymous user, unless the anonymous user also has access to your web service). Also, the code above is just an example - in production code there are quite a few other things to consider. Here is an msdn article that should help get you started.
FYI, the reason it works in development is most likely because your development server process runs as you, and since you have access to the web service, it succeeds.
I want to access shared resources on the other machine from code. My environment is a Sharepoint 2010 WebApplication working in Claims Authentication mode. Application's Windows identity is NT AUTHORITY\IUSR - not the user that has logged in, so to access the net share resources on the other machine I need to perform impersonation. Since I have no valid windows token to perform impersonation I need to use Claims To Windows Token Service, which I have configured to be able to be accessed by NT AUTHORITY\IUSR. The service is running as Local System account. I am able to get the impersonation level windows token from the service which I am using to perform impersonation using following code:
using (wi = S4UClient.UpnLogon(upn))
{
using(WindowsImpersonationContext wic2 = wi.Impersonate())
{
//code to access windows shares
}
}
The service is properly returning the token and the impersonation is successful in a way that when I return the current loggedin user's identity using following code:
WindowsIdentity.GetCurrent().Name;
I am getting the username of the user that is logged in to sharepoint.
The problem is that there is an "access denied" error when trying to access the network resources. The problem is surly because of the impersonation via c2wts since when I am providing the actual credentials (login and password) to impersonate the user using following code:
[DllImport("advapi32.dll", SetLastError = true)]
private static extern Int32 LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, Int32 dwLogonType, Int32 dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern Int32 ImpersonateLoggedOnUser(IntPtr hToken);
private static IntPtr ImpersonateUser(string user, string domain, string password)
{
IntPtr lnToken = new IntPtr(0);
Int32 TResult = LogonUser(user, domain, password, LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_WINNT50, ref lnToken);
if (TResult > 0)
{
ImpersonateLoggedOnUser(lnToken);
}
return lnToken;
}
I am able to enumerate shares on the server without any problem.
From the information I found on the internet to properly configure the service to access the sql database on another server I need to enable protocol transition and constrained delegation in the Active Directory and set that constrain to the mysql service. Still I am not sure how to enable delegation in this case since what I am trying to achieve is to be able to access any share on any machine, if only the user has permissions to do it.
To sum things up, I want to impersonate the user from within Sharepoint Claims Based Authentication WebApplication to access net shares on other computers but even tough impersonation using c2wts seems to be successful(correct username returned when checking current username) I am not able to access the resources on the other computer. On the other hand when impersonating using login and password provided directly in the code everything works ok, but that is not an option for me.
Ok I actually managed to solve it.
What I did was setting the impersonation in the web.config of my webapplication to false:
<identity impersonate="false" />
In that case I was able to see files on the local shares. To enable acccess in other computers I had to create Service Principal Name (SPN) for the account on which my webapplication was running.
SETSPN -A HTTP/ServerName myDomain\webAppAccountUserName
SETSPN -A HTTP/FQDNServerName myDomain\webAppAccountUserName
and SPN for the computer that webapplication and c2wts (c2wts is working as LocalSystem) was running
SETSPN -A HOST/ServerName ServerName
SETSPN -A HOST/FQDNServerName ServerName
Next step is to configure constrained delegation and protocol transitioning so we can delegate to the file share on the other computer to do that we need to open Active Directory Users And Computers Tool and configure delegation for the web application account and computer account that c2wts is working on. Specifically we need to:
select the account we are interested in, like computer account,
right click,
select properties,
select delegation tab,
select "Trust this computer for delegation to specified services only" and
"Use any authentication protocol",
add "common internet file system (cifs)" from the computer we want to connect to
We need to do exactly the same thing for the webapp account
Setting the value "Trust this computer for delegation to any service" will not work!
I have a service that is setup to retrieve a secure token from ADFS and use that token to communicate with other services. When I contact my ADFS windowsmixed endpoint from my local development machine hitting the ADFS service I am able to successfuly retrieve the token. However, when I install my service on the same machine that is running ADFS I receive the following error:
Secure channel cannot be opened because security negotiation with the remote endpoint has failed. This may be due to absent or incorrectly specified EndpointIdentity in the EndpointAddress used to create the channel. Please verify the EndpointIdentity specified or implied by the EndpointAddress correctly identifies the remote endpoint.
I am able to reproduce the error with the following code that simply gets the token. Again this code works when I am on my dev machine hitting the remote server, but it fails when on the server directly. I am using the same user credentials on both. I get the same error within the IIS web service using the app pool credentials and with a simple test client using the code below.
private static SecurityToken GetToken()
{
string stsEndpoint = "https://adfsserver.com/adfs/services/trust/13/windowsmixed";
string appliesTo = "http://domain.com/application/myapplication";
var factory = new WSTrustChannelFactory(
new WindowsWSTrustBinding(SecurityMode.TransportWithMessageCredential),
stsEndpoint);
factory.TrustVersion = TrustVersion.WSTrust13;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointAddress(appliesTo),
KeyType = KeyTypes.Symmetric
};
var channel = factory.CreateChannel();
return channel.Issue(rst);
}
I turned on tracing in the Windows Event Log for ADFS 2.0 debug. When hitting that windowsmixed endpoint directly on the server, I do not receive any entries which leads me to belive that it is not actually getting to the endpoint.
I do receive quite a few audit failures in the security log that are related to the services that I am running:
A handle to an object was requested.
Subject:
Security ID: DOMAIN\ODI$ODIController
Account Name: ODI$ODIController
Account Domain: DOMAIN
Logon ID: 0x1a574b5
Object:
Object Server: SC Manager
Object Type: SERVICE OBJECT
Object Name: WinHttpAutoProxySvc
Handle ID: 0x0
Process Information:
Process ID: 0x1f8
Process Name: C:\Windows\System32\services.exe
Access Request Information:
Transaction ID: {00000000-0000-0000-0000-000000000000}
Accesses: Query status of service
Start the service
Query information from service
Access Reasons: -
Access Mask: 0x94
Privileges Used for Access Check: -
I am able to access the usernamemixed endpoint using stored credentials and receive the proper token, so it seems to be something with authenticating the user to even be able to communicate with the ADFS endpoint.
If I set specific credentials in the code above, it is able to connect. Which again leads me to believe that it is not passing the correct credentials for my Windows user when on the same machine.
factory.Credentials.Windows.ClientCredential = new System.Net.NetworkCredential("UserID", "password1", "dev.domain");
Thank you for any assistance you can provide.
Brian
I had a similar issue. I was able to get it working using the example from here: http://blogs.southworks.net/mwoloski/2009/07/17/getting-a-token-from-adfs-ex-geneva-server-using-wcf/
The difference between your code and the working example is that you modify the message security to use the current security credentials in the binding rather than on the client. If you are using WIF 4.0, you need to modify the code to use a WSTrustChannelFactory instead of WSTrustClient. The other code doesn't change much though.
My code for the factory looks like this:
var binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;
binding.Security.Message.EstablishSecurityContext = false;
var factory = new WSTrustChannelFactory(
binding,
new EndpointAddress(new Uri(sts), EndpointIdentity.CreateUpnIdentity(adfsUpn)));