Der Zugriff auf eine Shared File (UNC) von einer entfernten, nicht vertrauenswürdiger Domäne mit Credentials
-
20-08-2019 - |
Frage
Wir haben in eine interessante Situation ausführen, die Bedürfnisse zu lösen, und meine Suche hat nill auftaucht. Deshalb appelliere ich an die SO Gemeinschaft um Hilfe.
Das Problem ist dies: wir haben das Bedürfnis programmatisch eine freigegebene Datei zuzugreifen, die nicht in unserer Domäne ist, und nicht innerhalb einer vertrauenswürdigen externen Domäne über Remote-File-Sharing / UNC. Natürlich müssen wir Anmeldeinformationen an den Remote-Computer liefern.
Normalerweise löst man dieses Problem in einer von zwei Möglichkeiten:
- Ordnen Sie die Dateifreigabe als Laufwerk und geben Sie die Anmeldeinformationen zu dieser Zeit. Dies wird in der Regel mit dem
NET USE
Befehl oder die Win32-Funktionen durchgeführt, dieNET USE
duplizieren. - Rufen Sie die Datei mit einem UNC-Pfad, als ob der Remote-Computer in der Domäne war und sicherzustellen, dass das Konto, unter dem das Programm läuft dupliziert (einschließlich Passwort) auf dem entfernten Rechner als lokalen Benutzer. Grundsätzlich nutzt die Tatsache, dass Windows automatisch die aktuellen Benutzer der Anmeldeinformationen liefern, wenn der Benutzer eine freigegebene Datei zuzugreifen versucht.
- Verwenden Sie keine Remote-Dateifreigabe. Verwenden Sie FTP (oder andere Mittel), um die Datei zu übertragen, die Arbeit an lokal, dann Transfer zurück.
Für verschiedene und verschiedene Gründe, unsere Sicherheit / Netzwerkarchitekten haben die ersten beiden Ansätze abgelehnt. Der zweite Ansatz ist offensichtlich ein Sicherheitsloch; wenn der Remote-Computer gefährdet ist, ist der lokale Computer jetzt in Gefahr. Der erste Ansatz ist nicht zufriedenstellend, da das neu bereitgestelltes Laufwerk während Dateizugriff durch das Programm eine gemeinsame Ressource zur Verfügung zu anderen Programmen auf dem lokalen Computer ist. Obwohl es durchaus möglich ist, diese vorübergehend zu machen, es ist immer noch ein Loch in ihrer Meinung.
Sie sind offen für die dritte Option, aber die Remote-Netzwerk-Administratoren bestehen auf SFTP statt FTPS und FtpWebRequest unterstützt nur FTPS. SFTP ist die weitere Firewall freundliche Option und es gibt ein paar Bibliotheken, die ich für diesen Ansatz verwenden könnte, aber ich würde es vorziehen, meine Abhängigkeiten zu verringern, wenn ich kann.
Ich habe MSDN für entweder durchsucht ein verwalteter oder ein win32 mittels Remote-File-Sharing, aber ich habe mit etwas Sinnvolles kommen schlug fehl.
Und so frage ich: Gibt es einen anderen Weg? Habe ich eine Super-Geheimnis win32-Funktion, die das tut, was ich will? Oder muss ich verfolgen eine Variante von Option 3?
Lösung
Die Art und Weise, Ihr Problem zu lösen, ist eine Win32-API Hier einige Beispiel-C # -Code, die WNetUseConnection verwendet.
Hinweis für die NETRESOURCE, sollten Sie null für die lpLocalName und lpProvider passieren. Die dwType sollte RESOURCETYPE_DISK sein. Die LpRemoteName sollte \\ Computer.
Andere Tipps
Für Menschen, für eine schnelle Lösung suchen, können Sie die NetworkShareAccesser
ich vor kurzem (basierend diese Antwort auf schrieb verwenden (Danke so viel)):
Verwendung:
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");
}
ACHTUNG: Achten Sie unbedingt darauf, dass Dispose
des NetworkShareAccesser
genannt wird (! Auch wenn Sie Abstürze App), sonst eine offene Verbindung wird unter Windows bleiben. Sie können alle offenen Verbindungen sehen, indem Sie die cmd
Aufforderung öffnen und geben Sie net use
.
Der Code:
/// <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);
}
}
AFAIK, Sie brauchen sich nicht zu Karte der UNC-Pfad zu einem Laufwerksbuchstaben, um Anmeldeinformationen für einen Server herzustellen. Ich Batch-Skripten regelmäßig verwendet wie:
net use \\myserver /user:username password
:: do something with \\myserver\the\file\i\want.xml
net use /delete \\my.server.com
kann jedoch jedes Programm auf das gleiche Konto wie Ihr Programm laufen würde noch in der Lage sein, alles zuzugreifen, hat Zugriff auf username:password
. Eine mögliche Lösung könnte darin bestehen, das Programm in seinem eigenen lokalen Benutzerkonto (der Zugriff auf das Konto lokalen UNC, die NET USE
genannt) zu isolieren.
Hinweis: Domains SMB accross Verwendung ist nicht ganz gut Nutzung der Technologie, IMO. Wenn die Sicherheit so wichtig ist, ist die Tatsache, dass SMB-Verschlüsselung fehlt, ist ein bisschen wie ein Dämpfer ganz von selbst.
Anstatt WNetUseConnection, würde ich empfehlen NetUseAdd . WNetUseConnection ist ein Vermächtnis-Funktion, die durch WNetUseConnection2 und WNetUseConnection3 abgelöst worden ist, aber alle diese Funktionen ein Netzwerkgerät erstellen, die in Windows Explorer sichtbar ist. NetUseAdd ist das Äquivalent Aufruf net use in einer DOS-Eingabeaufforderung auf einem entfernten Computer zu authentifizieren.
Wenn Sie NetUseAdd dann weitere Versuche nennen gelingt das Verzeichnis sollte zugreifen.
Während ich mich nicht kennen, würde ich sicherlich hoffen, dass # 2 falsch ist ... Ich würde gerne glauben, dass Windows wird sich nicht automatisch die Anmeldungsinformationen geben (am allerwenigsten mein Passwort vergessen!) zu jeder Maschine, geschweige denn eine, die nicht Teil meines Vertrauens.
Unabhängig davon, haben Sie die Personifizierung Architektur erforscht? Ihr Code wird ähnlich wie folgt aussehen:
using (System.Security.Principal.WindowsImpersonationContext context = System.Security.Principal.WindowsIdentity.Impersonate(token))
{
// Do network operations here
context.Undo();
}
In diesem Fall wird die token
Variable ist ein IntPtr. Um einen Wert für diese Variable zu erhalten, müssen Sie die nicht verwaltete Logonuser Windows-API-Funktion aufrufen müssen. Eine schnelle Reise nach pinvoke.net gibt uns die folgende Signatur:
[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
);
Benutzername, Domäne und Passwort sollte ziemlich offensichtlich zu sein scheinen. Werfen Sie einen Blick auf die verschiedenen Werte, die dwLogonType und dwLogonProvider übergeben werden können, um die man zu bestimmen, die am besten Ihren Bedürfnissen.
Dieser Code wurde nicht getestet, da ich hier nicht eine zweite Domäne haben, wo ich überprüfen kann, aber das sollte man hoffentlich auf dem richtigen Weg.
Die meisten SFTP-Server SCP unterstützen auch die viel einfacher sein können für Bibliotheken zu finden. Sie könnten auch nur einen vorhandenen Client aus dem Code aufrufen wie pscp enthalten mit PuTTY .
Wenn die Art der Datei mit dem Sie arbeiten etwas Einfaches wie eine Text- oder XML-Datei ist, könnte man sogar so weit gehen, wie Sie Ihre eigene Client / Server-Implementierung zu schreiben, die Datei mit so etwas wie .NET Remoting oder Web zu manipulieren Dienstleistungen.
Ich habe 3 gesehen Option implementiert mit JSCAPE Werkzeuge in einer ziemlich einfach Art und Weise . Sie könnten es versuchen. Es ist nicht kostenlos, aber es macht seinen Job.
Hier ist ein minimales POC Klasse w / alle cruft entfernt
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;
}
}
}
Sie können direkt \\server\share\folder
w / WNetUseConnection
verwenden, keine Notwendigkeit strippen es Teil \\server
nur vorher.
im befestigen meines vb.net Code basierend auf brian Referenz
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Public Class PinvokeWindowsNetworking
Const NO_ERROR As Integer = 0
Private Structure ErrorClass
Public num As Integer
Public message As String
Public Sub New(ByVal num As Integer, ByVal message As String)
Me.num = num
Me.message = message
End Sub
End Structure
Private Shared ERROR_LIST As ErrorClass() = New ErrorClass() {
New ErrorClass(5, "Error: Access Denied"),
New ErrorClass(85, "Error: Already Assigned"),
New ErrorClass(1200, "Error: Bad Device"),
New ErrorClass(67, "Error: Bad Net Name"),
New ErrorClass(1204, "Error: Bad Provider"),
New ErrorClass(1223, "Error: Cancelled"),
New ErrorClass(1208, "Error: Extended Error"),
New ErrorClass(487, "Error: Invalid Address"),
New ErrorClass(87, "Error: Invalid Parameter"),
New ErrorClass(1216, "Error: Invalid Password"),
New ErrorClass(234, "Error: More Data"),
New ErrorClass(259, "Error: No More Items"),
New ErrorClass(1203, "Error: No Net Or Bad Path"),
New ErrorClass(1222, "Error: No Network"),
New ErrorClass(1206, "Error: Bad Profile"),
New ErrorClass(1205, "Error: Cannot Open Profile"),
New ErrorClass(2404, "Error: Device In Use"),
New ErrorClass(2250, "Error: Not Connected"),
New ErrorClass(2401, "Error: Open Files")}
Private Shared Function getErrorForNumber(ByVal errNum As Integer) As String
For Each er As ErrorClass In ERROR_LIST
If er.num = errNum Then Return er.message
Next
Try
Throw New Win32Exception(errNum)
Catch ex As Exception
Return "Error: Unknown, " & errNum & " " & ex.Message
End Try
Return "Error: Unknown, " & errNum
End Function
<DllImport("Mpr.dll")>
Private Shared Function WNetUseConnection(ByVal hwndOwner As IntPtr, ByVal lpNetResource As NETRESOURCE, ByVal lpPassword As String, ByVal lpUserID As String, ByVal dwFlags As Integer, ByVal lpAccessName As String, ByVal lpBufferSize As String, ByVal lpResult As String) As Integer
End Function
<DllImport("Mpr.dll")>
Private Shared Function WNetCancelConnection2(ByVal lpName As String, ByVal dwFlags As Integer, ByVal fForce As Boolean) As Integer
End Function
<StructLayout(LayoutKind.Sequential)>
Private Class NETRESOURCE
Public dwScope As Integer = 0
Public dwType As Integer = 0
Public dwDisplayType As Integer = 0
Public dwUsage As Integer = 0
Public lpLocalName As String = ""
Public lpRemoteName As String = ""
Public lpComment As String = ""
Public lpProvider As String = ""
End Class
Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String) As String
Return connectToRemote(remoteUNC, username, password, False)
End Function
Public Shared Function connectToRemote(ByVal remoteUNC As String, ByVal username As String, ByVal password As String, ByVal promptUser As Boolean) As String
Dim nr As NETRESOURCE = New NETRESOURCE()
nr.dwType = ResourceTypes.Disk
nr.lpRemoteName = remoteUNC
Dim ret As Integer
If promptUser Then
ret = WNetUseConnection(IntPtr.Zero, nr, "", "", Connects.Interactive Or Connects.Prompt, Nothing, Nothing, Nothing)
Else
ret = WNetUseConnection(IntPtr.Zero, nr, password, username, 0, Nothing, Nothing, Nothing)
End If
If ret = NO_ERROR Then Return Nothing
Return getErrorForNumber(ret)
End Function
Public Shared Function disconnectRemote(ByVal remoteUNC As String) As String
Dim ret As Integer = WNetCancelConnection2(remoteUNC, Connects.UpdateProfile, False)
If ret = NO_ERROR Then Return Nothing
Return getErrorForNumber(ret)
End Function
Enum Resources As Integer
Connected = &H1
GlobalNet = &H2
Remembered = &H3
End Enum
Enum ResourceTypes As Integer
Any = &H0
Disk = &H1
Print = &H2
End Enum
Enum ResourceDisplayTypes As Integer
Generic = &H0
Domain = &H1
Server = &H2
Share = &H3
File = &H4
Group = &H5
End Enum
Enum ResourceUsages As Integer
Connectable = &H1
Container = &H2
End Enum
Enum Connects As Integer
Interactive = &H8
Prompt = &H10
Redirect = &H80
UpdateProfile = &H1
CommandLine = &H800
CmdSaveCred = &H1000
LocalDrive = &H100
End Enum
End Class
wie es zu benutzen
Dim login = PinvokeWindowsNetworking.connectToRemote("\\ComputerName", "ComputerName\UserName", "Password")
If IsNothing(login) Then
'do your thing on the shared folder
PinvokeWindowsNetworking.disconnectRemote("\\ComputerName")
End If
sah ich auf MS die Antworten zu finden. Die erste Lösung übernimmt das Benutzerkonto läuft das Bewerbungsverfahren Zugriff auf den freigegebenen Ordner oder das Laufwerk hat (gleiche Domain). Stellen Sie sicher, dass Ihre DNS aufgelöst wird, oder versuchen, IP-Adresse. Einfach wie folgt vor:
DirectoryInfo di = new DirectoryInfo(PATH);
var files = di.EnumerateFiles("*.*", SearchOption.AllDirectories);
Wenn Sie zwischen verschiedenen Domänen .NET 2.0 mit Credentials diesem Modell folgen:
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();