c# - Acceso a un archivo compartido(UNC) desde un dominio remoto no confiable con credenciales
windows file-sharing (9)
AFAIK, no necesita asignar la ruta UNC a una letra de unidad para establecer credenciales para un servidor. Solía utilizar secuencias de comandos por lotes como:
net use //myserver /user:username password
:: do something with //myserver/the/file/i/want.xml
net use /delete //my.server.com
Sin embargo, cualquier programa que se ejecute en la misma cuenta que su programa aún podría acceder a todo ese username:password
tiene acceso. Una posible solución podría ser aislar su programa en su propia cuenta de usuario local (el acceso de UNC es local a la cuenta que llamó NET USE
).
Nota: Usar SMB en varios dominios no es un buen uso de la tecnología, IMO. Si la seguridad es tan importante, el hecho de que SMB carece de encriptación es un poco difícil.
Nos encontramos con una situación interesante que necesita solución, y mis búsquedas han aparecido hasta ahora. Por lo tanto, hago un llamamiento a la comunidad SO para obtener ayuda.
El problema es este: tenemos la necesidad de acceder mediante programación a un archivo compartido que no está en nuestro dominio, y no está dentro de un dominio externo de confianza a través de compartir archivos remotos / UNC. Naturalmente, necesitamos proporcionar credenciales a la máquina remota.
Típicamente, uno resuelve este problema de una de dos maneras:
- Asigne el recurso compartido de archivos como una unidad y proporcione las credenciales en ese momento. Esto normalmente se hace usando el comando
NET USE
o las funciones Win32 que duplicanNET USE
. - Acceda al archivo con una ruta UNC como si la computadora remota estuviera en el dominio y asegúrese de que la cuenta en la que se ejecuta el programa esté duplicada (incluida la contraseña) en la máquina remota como un usuario local. Básicamente aprovechar el hecho de que Windows proporcionará automáticamente las credenciales del usuario actual cuando el usuario intente acceder a un archivo compartido.
- No use archivos compartidos remotos. Use FTP (u otro medio) para transferir el archivo, trabaje en él localmente y luego transfiéralo nuevamente.
Por diversos motivos, nuestros arquitectos de seguridad / red han rechazado los dos primeros enfoques. El segundo enfoque es obviamente un agujero de seguridad; si la computadora remota está en peligro, la computadora local ahora está en riesgo. El primer enfoque es insatisfactorio porque la unidad recientemente montada es un recurso compartido disponible para otros programas en la computadora local durante el acceso al archivo por parte del programa. A pesar de que es bastante posible hacer que esto sea temporal, sigue siendo un agujero en su opinión.
Están abiertos a la tercera opción, pero los administradores de red remota insisten en SFTP en lugar de FTPS, y FtpWebRequest solo es compatible con FTPS. SFTP es la opción más amigable con el firewall y hay un par de bibliotecas que podría usar para ese enfoque, pero preferiría reducir mis dependencias si pudiera.
He buscado en MSDN un medio administrado o win32 de uso compartido de archivos remoto, pero no he podido encontrar nada útil.
Y entonces pregunto: ¿Hay otra manera? ¿Extrañé una función supersecreta de win32 que hace lo que quiero? ¿O debo buscar alguna variante de la opción 3?
Aquí una clase POC mínima con todo el material eliminado
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
public class UncShareWithCredentials : IDisposable
{
private string _uncShare;
public UncShareWithCredentials(string uncShare, string userName, string password)
{
var nr = new Native.NETRESOURCE
{
dwType = Native.RESOURCETYPE_DISK,
lpRemoteName = uncShare
};
int result = Native.WNetUseConnection(IntPtr.Zero, nr, password, userName, 0, null, null, null);
if (result != Native.NO_ERROR)
{
throw new Win32Exception(result);
}
_uncShare = uncShare;
}
public void Dispose()
{
if (!string.IsNullOrEmpty(_uncShare))
{
Native.WNetCancelConnection2(_uncShare, Native.CONNECT_UPDATE_PROFILE, false);
_uncShare = null;
}
}
private class Native
{
public const int RESOURCETYPE_DISK = 0x00000001;
public const int CONNECT_UPDATE_PROFILE = 0x00000001;
public const int NO_ERROR = 0;
[DllImport("mpr.dll")]
public static extern int WNetUseConnection(IntPtr hwndOwner, NETRESOURCE lpNetResource, string lpPassword, string lpUserID,
int dwFlags, string lpAccessName, string lpBufferSize, string lpResult);
[DllImport("mpr.dll")]
public static extern int WNetCancelConnection2(string lpName, int dwFlags, bool fForce);
[StructLayout(LayoutKind.Sequential)]
public class NETRESOURCE
{
public int dwScope;
public int dwType;
public int dwDisplayType;
public int dwUsage;
public string lpLocalName;
public string lpRemoteName;
public string lpComment;
public string lpProvider;
}
}
}
Puede usar directamente //server/share/folder
w / WNetUseConnection
, no es necesario WNetUseConnection
en //server
parte del //server
solo de antemano.
En lugar de WNetUseConnection, recomendaría NetUseAdd . WNetUseConnection es una función heredada que ha sido reemplazada por WNetUseConnection2 y WNetUseConnection3, pero todas esas funciones crean un dispositivo de red visible en el Explorador de Windows. NetUseAdd es el equivalente de llamar al uso de red en un indicador de DOS para autenticarse en una computadora remota.
Si llama a NetUseAdd, los intentos subsiguientes de acceder al directorio deberían tener éxito.
He visto la opción 3 implementada con herramientas JScape de una manera bastante directa. Puede intentarlo. No es gratis, pero cumple su función.
La forma de resolver su problema es usar una API de Win32 llamada WNetUseConnection .
Utilice esta función para conectarse a una ruta UNC con autenticación, NO para mapear una unidad .
Esto le permitirá conectarse a una máquina remota, incluso si no está en el mismo dominio, e incluso si tiene un nombre de usuario y contraseña diferentes.
Una vez que haya utilizado WNetUseConnection, podrá acceder al archivo a través de una ruta UNC como si estuviera en el mismo dominio. La mejor manera es probablemente a través de las acciones administrativas integradas.
Ejemplo: // computername / c $ / program files / Folder / file.txt
Aquí hay algunos ejemplos de código C # que usa WNetUseConnection .
Tenga en cuenta que para NetResource, debe pasar null para lpLocalName y lpProvider. El dwType debe ser RESOURCETYPE_DISK. LpRemoteName debe ser // ComputerName.
La mayoría de los servidores SFTP también son compatibles con SCP, por lo que puede ser mucho más fácil encontrar bibliotecas. Incluso podría llamar a un cliente existente desde su código como pscp incluido con PuTTY .
Si el tipo de archivo con el que está trabajando es algo simple como un archivo de texto o XML, incluso puede escribir su propia implementación cliente / servidor para manipular el archivo usando algo como .NET Remoting o servicios web.
Miré a MS para encontrar las respuestas. La primera solución asume que la cuenta de usuario que ejecuta el proceso de la aplicación tiene acceso a la carpeta o unidad compartida (Mismo dominio). Asegúrese de que su DNS esté resuelto o intente usar la dirección IP. Simplemente haz lo siguiente:
DirectoryInfo di = new DirectoryInfo(PATH);
var files = di.EnumerateFiles("*.*", SearchOption.AllDirectories);
Si desea acceder a diferentes dominios .NET 2.0 con credenciales, siga este modelo:
WebRequest req = FileWebRequest.Create(new Uri(@"//<server Name>/Dir/test.txt"));
req.Credentials = new NetworkCredential(@"<Domain>/<User>", "<Password>");
req.PreAuthenticate = true;
WebResponse d = req.GetResponse();
FileStream fs = File.Create("test.txt");
// here you can check that the cast was successful if you want.
fs = d.GetResponseStream() as FileStream;
fs.Close();
Para las personas que buscan una solución rápida, pueden usar el NetworkShareAccesser
que escribí recientemente (basado en esta respuesta (¡muchas gracias!)):
Uso:
using (NetworkShareAccesser.Access(REMOTE_COMPUTER_NAME, DOMAIN, USER_NAME, PASSWORD))
{
File.Copy(@"C:/Some/File/To/copy.txt", @"//REMOTE-COMPUTER/My/Shared/Target/file.txt");
}
ADVERTENCIA: asegúrese por NetworkShareAccesser
se llame a NetworkShareAccesser
del NetworkShareAccesser
(¡incluso si la aplicación falla!); De lo contrario, la conexión permanecerá en Windows. Puede ver todas las conexiones abiertas abriendo el indicador de cmd
e ingrese net use
.
El código:
/// <summary>
/// Provides access to a network share.
/// </summary>
public class NetworkShareAccesser : IDisposable
{
private string _remoteUncName;
private string _remoteComputerName;
public string RemoteComputerName
{
get
{
return this._remoteComputerName;
}
set
{
this._remoteComputerName = value;
this._remoteUncName = @"//" + this._remoteComputerName;
}
}
public string UserName
{
get;
set;
}
public string Password
{
get;
set;
}
#region Consts
private const int RESOURCE_CONNECTED = 0x00000001;
private const int RESOURCE_GLOBALNET = 0x00000002;
private const int RESOURCE_REMEMBERED = 0x00000003;
private const int RESOURCETYPE_ANY = 0x00000000;
private const int RESOURCETYPE_DISK = 0x00000001;
private const int RESOURCETYPE_PRINT = 0x00000002;
private const int RESOURCEDISPLAYTYPE_GENERIC = 0x00000000;
private const int RESOURCEDISPLAYTYPE_DOMAIN = 0x00000001;
private const int RESOURCEDISPLAYTYPE_SERVER = 0x00000002;
private const int RESOURCEDISPLAYTYPE_SHARE = 0x00000003;
private const int RESOURCEDISPLAYTYPE_FILE = 0x00000004;
private const int RESOURCEDISPLAYTYPE_GROUP = 0x00000005;
private const int RESOURCEUSAGE_CONNECTABLE = 0x00000001;
private const int RESOURCEUSAGE_CONTAINER = 0x00000002;
private const int CONNECT_INTERACTIVE = 0x00000008;
private const int CONNECT_PROMPT = 0x00000010;
private const int CONNECT_REDIRECT = 0x00000080;
private const int CONNECT_UPDATE_PROFILE = 0x00000001;
private const int CONNECT_COMMANDLINE = 0x00000800;
private const int CONNECT_CMD_SAVECRED = 0x00001000;
private const int CONNECT_LOCALDRIVE = 0x00000100;
#endregion
#region Errors
private const int NO_ERROR = 0;
private const int ERROR_ACCESS_DENIED = 5;
private const int ERROR_ALREADY_ASSIGNED = 85;
private const int ERROR_BAD_DEVICE = 1200;
private const int ERROR_BAD_NET_NAME = 67;
private const int ERROR_BAD_PROVIDER = 1204;
private const int ERROR_CANCELLED = 1223;
private const int ERROR_EXTENDED_ERROR = 1208;
private const int ERROR_INVALID_ADDRESS = 487;
private const int ERROR_INVALID_PARAMETER = 87;
private const int ERROR_INVALID_PASSWORD = 1216;
private const int ERROR_MORE_DATA = 234;
private const int ERROR_NO_MORE_ITEMS = 259;
private const int ERROR_NO_NET_OR_BAD_PATH = 1203;
private const int ERROR_NO_NETWORK = 1222;
private const int ERROR_BAD_PROFILE = 1206;
private const int ERROR_CANNOT_OPEN_PROFILE = 1205;
private const int ERROR_DEVICE_IN_USE = 2404;
private const int ERROR_NOT_CONNECTED = 2250;
private const int ERROR_OPEN_FILES = 2401;
#endregion
#region PInvoke Signatures
[DllImport("Mpr.dll")]
private static extern int WNetUseConnection(
IntPtr hwndOwner,
NETRESOURCE lpNetResource,
string lpPassword,
string lpUserID,
int dwFlags,
string lpAccessName,
string lpBufferSize,
string lpResult
);
[DllImport("Mpr.dll")]
private static extern int WNetCancelConnection2(
string lpName,
int dwFlags,
bool fForce
);
[StructLayout(LayoutKind.Sequential)]
private class NETRESOURCE
{
public int dwScope = 0;
public int dwType = 0;
public int dwDisplayType = 0;
public int dwUsage = 0;
public string lpLocalName = "";
public string lpRemoteName = "";
public string lpComment = "";
public string lpProvider = "";
}
#endregion
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name. The user will be promted to enter credentials
/// </summary>
/// <param name="remoteComputerName"></param>
/// <returns></returns>
public static NetworkShareAccesser Access(string remoteComputerName)
{
return new NetworkShareAccesser(remoteComputerName);
}
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name using the given domain/computer name, username and password
/// </summary>
/// <param name="remoteComputerName"></param>
/// <param name="domainOrComuterName"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
public static NetworkShareAccesser Access(string remoteComputerName, string domainOrComuterName, string userName, string password)
{
return new NetworkShareAccesser(remoteComputerName,
domainOrComuterName + @"/" + userName,
password);
}
/// <summary>
/// Creates a NetworkShareAccesser for the given computer name using the given username (format: domainOrComputername/Username) and password
/// </summary>
/// <param name="remoteComputerName"></param>
/// <param name="userName"></param>
/// <param name="password"></param>
public static NetworkShareAccesser Access(string remoteComputerName, string userName, string password)
{
return new NetworkShareAccesser(remoteComputerName,
userName,
password);
}
private NetworkShareAccesser(string remoteComputerName)
{
RemoteComputerName = remoteComputerName;
this.ConnectToShare(this._remoteUncName, null, null, true);
}
private NetworkShareAccesser(string remoteComputerName, string userName, string password)
{
RemoteComputerName = remoteComputerName;
UserName = userName;
Password = password;
this.ConnectToShare(this._remoteUncName, this.UserName, this.Password, false);
}
private void ConnectToShare(string remoteUnc, string username, string password, bool promptUser)
{
NETRESOURCE nr = new NETRESOURCE
{
dwType = RESOURCETYPE_DISK,
lpRemoteName = remoteUnc
};
int result;
if (promptUser)
{
result = WNetUseConnection(IntPtr.Zero, nr, "", "", CONNECT_INTERACTIVE | CONNECT_PROMPT, null, null, null);
}
else
{
result = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, null, null, null);
}
if (result != NO_ERROR)
{
throw new Win32Exception(result);
}
}
private void DisconnectFromShare(string remoteUnc)
{
int result = WNetCancelConnection2(remoteUnc, CONNECT_UPDATE_PROFILE, false);
if (result != NO_ERROR)
{
throw new Win32Exception(result);
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose()
{
this.DisconnectFromShare(this._remoteUncName);
}
}
Si bien no me conozco a mí mismo, ciertamente espero que el # 2 sea incorrecto ... Me gustaría pensar que Windows no va a AUTOMÁTICAMENTE dar mi información de inicio de sesión (¡menos que mi contraseña!) A cualquier máquina , y mucho menos uno que no es parte de mi confianza.
De todos modos, ¿has explorado la arquitectura de la suplantación? Tu código se verá similar a esto:
using (System.Security.Principal.WindowsImpersonationContext context = System.Security.Principal.WindowsIdentity.Impersonate(token))
{
// Do network operations here
context.Undo();
}
En este caso, la variable token
es un IntPtr. Para obtener un valor para esta variable, deberá llamar a la función de API de Windows LogonUser no administrada. Un viaje rápido a pinvoke.net nos da la siguiente firma:
[System.Runtime.InteropServices.DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
out IntPtr phToken
);
El nombre de usuario, el dominio y la contraseña deberían parecer bastante obvios. Eche un vistazo a los diversos valores que se pueden pasar a dwLogonType y dwLogonProvider para determinar el que mejor se adapte a sus necesidades.
Este código no ha sido probado, ya que no tengo un segundo dominio aquí donde pueda verificarlo, pero espero que esto te ponga en el camino correcto.