Update: Mac Enrol using SshNet

SshNet supports required cryptographic algorithms. Simplified data
gathering using plist/xml format from system_profiler. Fixes #88 and #92
This commit is contained in:
Gary Sharp
2016-07-18 18:54:01 +10:00
parent 4e6093702d
commit 44f6d325db
5 changed files with 119 additions and 142 deletions
+2
View File
@@ -3,6 +3,8 @@
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
.vs/
# User-specific files # User-specific files
*.suo *.suo
*.user *.user
+106 -136
View File
@@ -10,7 +10,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Tamir.SharpSsh; using Renci.SshNet;
using PList;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Disco.BI.DeviceBI namespace Disco.BI.DeviceBI
{ {
@@ -24,7 +28,6 @@ namespace Disco.BI.DeviceBI
Register = 30 Register = 30
} }
private static Regex SshPromptRegEx = new Regex("[\\$,\\#]", RegexOptions.Multiline);
public static MacSecureEnrolResponse MacSecureEnrol(DiscoDataContext Database, string Host) public static MacSecureEnrolResponse MacSecureEnrol(DiscoDataContext Database, string Host)
{ {
MacEnrol trustedRequest = new MacEnrol(); MacEnrol trustedRequest = new MacEnrol();
@@ -33,89 +36,112 @@ namespace Disco.BI.DeviceBI
try try
{ {
EnrolmentLog.LogSessionStarting(sessionId, Host, EnrolmentTypes.MacSecure); EnrolmentLog.LogSessionStarting(sessionId, Host, EnrolmentTypes.MacSecure);
EnrolmentLog.LogSessionProgress(sessionId, 0, string.Format("Connecting to '{0}' as '{1}'", Host, Database.DiscoConfiguration.Bootstrapper.MacSshUsername)); EnrolmentLog.LogSessionProgress(sessionId, 0, $"Connecting to '{Host}' as '{Database.DiscoConfiguration.Bootstrapper.MacSshUsername}'");
SshShell shell = new SshShell(Host, Database.DiscoConfiguration.Bootstrapper.MacSshUsername, Database.DiscoConfiguration.Bootstrapper.MacSshPassword);
try var sshConnectionInfo = new KeyboardInteractiveConnectionInfo(Host, Database.DiscoConfiguration.Bootstrapper.MacSshUsername);
sshConnectionInfo.AuthenticationPrompt += (sender, e) =>
{ {
shell.ExpectPattern = "#"; foreach (var prompt in e.Prompts)
shell.Connect();
EnrolmentLog.LogSessionProgress(sessionId, 10, "Connected, Authenticating");
var output = shell.Expect(SshPromptRegEx);
bool sessionElevated = false;
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, output);
if (!output.TrimEnd(new char[0]).EndsWith("#"))
{ {
EnrolmentLog.LogSessionProgress(sessionId, 22, "Connected, Elevating Credentials"); if (prompt.Request.StartsWith("Password", StringComparison.OrdinalIgnoreCase))
shell.WriteLine("sudo -k");
System.Threading.Thread.Sleep(250);
output = shell.Expect(SshPromptRegEx);
EnrolmentLog.LogSessionProgress(sessionId, 25, "Connected, Elevating Credentials");
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, output);
shell.WriteLine("sudo -s -S");
System.Threading.Thread.Sleep(250);
output = shell.Expect(":");
EnrolmentLog.LogSessionProgress(sessionId, 27, "Connected, Elevating Credentials");
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, output);
shell.WriteLine(Database.DiscoConfiguration.Bootstrapper.MacSshPassword);
System.Threading.Thread.Sleep(250);
output = shell.Expect(SshPromptRegEx);
sessionElevated = true;
EnrolmentLog.LogSessionDiagnosticInformation(sessionId, output);
}
EnrolmentLog.LogSessionProgress(sessionId, 20, "Retrieving Serial Number");
trustedRequest.DeviceSerialNumber = ParseMacShellCommand(shell, "system_profiler SPHardwareDataType | grep \"Serial Number\" | cut -d \":\" -f 2-", sessionId);
EnrolmentLog.LogSessionDevice(sessionId, trustedRequest.DeviceSerialNumber, null);
EnrolmentLog.LogSessionProgress(sessionId, 30, "Retrieving Hardware UUID");
trustedRequest.DeviceUUID = ParseMacShellCommand(shell, "system_profiler SPHardwareDataType | grep \"Hardware UUID:\" | cut -d \":\" -f 2-", sessionId);
EnrolmentLog.LogSessionProgress(sessionId, 40, "Retrieving Computer Name");
trustedRequest.DeviceComputerName = ParseMacShellCommand(shell, "scutil --get ComputerName", sessionId);
EnrolmentLog.LogSessionProgress(sessionId, 50, "Retrieving Ethernet MAC Address");
string lanNicId = ParseMacShellCommand(shell, "system_profiler SPEthernetDataType | egrep -o \"en0|en1|en2|en3|en4|en5|en6\"", sessionId);
if (!string.IsNullOrWhiteSpace(lanNicId))
{
trustedRequest.DeviceLanMacAddress = ParseMacShellCommand(shell, string.Format("ifconfig {0} | grep ether | cut -d \" \" -f 2-", lanNicId), sessionId);
}
EnrolmentLog.LogSessionProgress(sessionId, 65, "Retrieving Wireless MAC Address");
string wlanNicId = ParseMacShellCommand(shell, "system_profiler SPAirPortDataType | egrep -o \"en0|en1|en2|en3|en4|en5|en6\"", sessionId);
if (!string.IsNullOrWhiteSpace(wlanNicId))
{
trustedRequest.DeviceWlanMacAddress = ParseMacShellCommand(shell, string.Format("ifconfig {0} | grep ether | cut -d \" \" -f 2-", wlanNicId), sessionId);
}
trustedRequest.DeviceManufacturer = "Apple Inc.";
EnrolmentLog.LogSessionProgress(sessionId, 80, "Retrieving Model");
trustedRequest.DeviceModel = ParseMacShellCommand(shell, "system_profiler SPHardwareDataType | grep \"Model Identifier:\" | cut -d \":\" -f 2-", sessionId);
EnrolmentLog.LogSessionProgress(sessionId, 90, "Retrieving Model Type");
trustedRequest.DeviceModelType = ParseMacModelType(ParseMacShellCommand(shell, "system_profiler SPHardwareDataType | grep \"Model Name:\" | cut -d \":\" -f 2-", sessionId));
EnrolmentLog.LogSessionProgress(sessionId, 99, "Disconnecting");
output = ParseMacModelType(ParseMacShellCommand(shell, "exit", sessionId));
if (sessionElevated)
{
output = ParseMacModelType(ParseMacShellCommand(shell, "exit", sessionId));
}
if (shell.Connected)
{
shell.Close();
}
EnrolmentLog.LogSessionProgress(sessionId, 100, "Disconnected, Starting Disco Enrolment");
MacSecureEnrolResponse response = MacSecureEnrolResponse.FromMacEnrolResponse(MacEnrol(Database, trustedRequest, true, sessionId));
EnrolmentLog.LogSessionFinished(sessionId);
MacSecureEnrol = response;
}
catch (System.Exception ex)
{
throw ex;
}
finally
{
if (shell != null)
{
bool connected = shell.Connected;
if (connected)
{ {
shell.Close(); EnrolmentLog.LogSessionProgress(sessionId, 10, $"Authenticating at '{Host}' as '{Database.DiscoConfiguration.Bootstrapper.MacSshUsername}'");
prompt.Response = Database.DiscoConfiguration.Bootstrapper.MacSshPassword;
}
}
};
using (var sshClient = new SshClient(sshConnectionInfo))
{
sshClient.Connect();
try
{
EnrolmentLog.LogSessionProgress(sessionId, 30, "Retrieving System Profile Information");
var sshResult = sshClient.RunCommand("system_profiler -xml SPHardwareDataType SPNetworkDataType SPSoftwareDataType");
PListRoot profilerData;
using (var reader = new StringReader(sshResult.Result))
{
using (var xmlReader = XmlReader.Create(reader, new XmlReaderSettings() { DtdProcessing = DtdProcessing.Ignore }))
{
XmlSerializer serializer = new XmlSerializer(typeof(PListRoot));
profilerData = (PListRoot)serializer.Deserialize(xmlReader);
}
}
EnrolmentLog.LogSessionProgress(sessionId, 90, "Processing System Profile Information");
PListDict profilerDataHardware = null;
PListArray profilerDataNetwork = null;
PListDict profilerDataSoftware = null;
foreach (PListDict node in (profilerData.Root as PListArray))
{
var nodeItems = ((PListArray)node["_items"]);
switch (((PListString)node["_dataType"]).Value)
{
case "SPHardwareDataType":
profilerDataHardware = (PListDict)nodeItems[0];
break;
case "SPNetworkDataType":
profilerDataNetwork = nodeItems;
break;
case "SPSoftwareDataType":
profilerDataSoftware = (PListDict)nodeItems[0];
break;
}
}
if (profilerDataHardware == null || profilerDataNetwork == null || profilerDataSoftware == null)
throw new InvalidOperationException("System Profiler didn't return information for a requested data type");
trustedRequest.DeviceSerialNumber = (profilerDataHardware["serial_number"] as PListString).Value;
trustedRequest.DeviceUUID = (profilerDataHardware["platform_UUID"] as PListString).Value;
trustedRequest.DeviceComputerName = (profilerDataSoftware["local_host_name"] as PListString).Value;
var profilerDataNetworkEthernet = profilerDataNetwork.Cast<PListDict>().FirstOrDefault(e => ((PListString)e["_name"]).Value == "Ethernet");
if (profilerDataNetworkEthernet != null)
{
trustedRequest.DeviceLanMacAddress = ((PListString)(profilerDataNetworkEthernet["Ethernet"] as PListDict)["MAC Address"]).Value;
}
var profilerDataNetworkWiFi = profilerDataNetwork.Cast<PListDict>().FirstOrDefault(e => ((PListString)e["_name"]).Value == "Wi-Fi");
if (profilerDataNetworkWiFi != null)
{
trustedRequest.DeviceWlanMacAddress = ((PListString)(profilerDataNetworkWiFi["Ethernet"] as PListDict)["MAC Address"]).Value;
}
trustedRequest.DeviceManufacturer = "Apple Inc.";
trustedRequest.DeviceModel = (profilerDataHardware["machine_model"] as PListString).Value;
trustedRequest.DeviceModelType = ParseMacModelType((profilerDataHardware["machine_name"] as PListString).Value);
EnrolmentLog.LogSessionProgress(sessionId, 99, "Disconnecting");
sshClient.Disconnect();
}
catch (Exception)
{
throw;
}
finally
{
if (sshClient != null)
{
bool connected = sshClient.IsConnected;
if (connected)
{
sshClient.Disconnect();
}
} }
} }
} }
EnrolmentLog.LogSessionProgress(sessionId, 100, "Disconnected, Starting Disco Enrolment");
MacSecureEnrolResponse response = MacSecureEnrolResponse.FromMacEnrolResponse(MacEnrol(Database, trustedRequest, true, sessionId));
EnrolmentLog.LogSessionFinished(sessionId);
MacSecureEnrol = response;
} }
catch (System.Exception ex) catch (System.Exception ex)
{ {
@@ -123,6 +149,7 @@ namespace Disco.BI.DeviceBI
EnrolmentLog.LogSessionError(sessionId, ex); EnrolmentLog.LogSessionError(sessionId, ex);
throw ex; throw ex;
} }
return MacSecureEnrol; return MacSecureEnrol;
} }
@@ -154,63 +181,6 @@ namespace Disco.BI.DeviceBI
return ParseMacModelType; return ParseMacModelType;
} }
private static string ParseMacShellCommand(SshShell Shell, string Command, string LogSessionId)
{
Shell.WriteLine(Command);
System.Threading.Thread.Sleep(250);
string Response = Shell.Expect(SshPromptRegEx);
Response = Response.Replace("\r", string.Empty);
EnrolmentLog.LogSessionDiagnosticInformation(LogSessionId, Response);
bool flag = Response.Contains("\n");
string ParseMacShellCommand;
if (flag)
{
string[] ResponseLines = Response.Split(new char[]
{
'\n'
});
switch (ResponseLines.Length)
{
case 0:
case 1:
{
ParseMacShellCommand = string.Empty;
break;
}
case 2:
case 3:
{
ParseMacShellCommand = ResponseLines[1].Trim();
break;
}
default:
{
System.Text.StringBuilder ResponseBuilder = new System.Text.StringBuilder();
int num = ResponseLines.Length - 2;
int lineIndex = 1;
while (true)
{
int arg_111_0 = lineIndex;
int num2 = num;
if (arg_111_0 > num2)
{
break;
}
ResponseBuilder.AppendLine(ResponseLines[lineIndex]);
lineIndex++;
}
ParseMacShellCommand = ResponseBuilder.ToString().Trim();
break;
}
}
}
else
{
ParseMacShellCommand = Response;
}
return ParseMacShellCommand;
}
#endregion #endregion
public static MacEnrolResponse MacEnrol(DiscoDataContext Database, MacEnrol Request, bool Trusted, string OpenSessionId = null) public static MacEnrolResponse MacEnrol(DiscoDataContext Database, MacEnrol Request, bool Trusted, string OpenSessionId = null)
+7 -3
View File
@@ -54,9 +54,16 @@
<Reference Include="itextsharp"> <Reference Include="itextsharp">
<HintPath>..\Resources\Libraries\iTextSharp\itextsharp.dll</HintPath> <HintPath>..\Resources\Libraries\iTextSharp\itextsharp.dll</HintPath>
</Reference> </Reference>
<Reference Include="PList, Version=0.1.4109.38751, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\plist.net.1.0\lib\Net35\PList.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Quartz"> <Reference Include="Quartz">
<HintPath>..\Resources\Libraries\Quartz\Quartz.dll</HintPath> <HintPath>..\Resources\Libraries\Quartz\Quartz.dll</HintPath>
</Reference> </Reference>
<Reference Include="Renci.SshNet">
<HintPath>..\..\..\Resources\Libraries\SshNet\Renci.SshNet.dll</HintPath>
</Reference>
<Reference Include="Spring.Core"> <Reference Include="Spring.Core">
<HintPath>..\Resources\Libraries\Spring.NET\Spring.Core.dll</HintPath> <HintPath>..\Resources\Libraries\Spring.NET\Spring.Core.dll</HintPath>
</Reference> </Reference>
@@ -99,9 +106,6 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="Tamir.SharpSSH">
<HintPath>..\Resources\Libraries\SharpSSH\Tamir.SharpSSH.dll</HintPath>
</Reference>
<Reference Include="zxing"> <Reference Include="zxing">
<HintPath>..\Resources\Libraries\ZXing\zxing.dll</HintPath> <HintPath>..\Resources\Libraries\ZXing\zxing.dll</HintPath>
</Reference> </Reference>
+1
View File
@@ -5,6 +5,7 @@
<package id="Microsoft.Bcl" version="1.1.9" targetFramework="net45" /> <package id="Microsoft.Bcl" version="1.1.9" targetFramework="net45" />
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net45" /> <package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net45" />
<package id="Microsoft.Net.Http" version="2.2.22" targetFramework="net45" /> <package id="Microsoft.Net.Http" version="2.2.22" targetFramework="net45" />
<package id="plist.net" version="1.0" targetFramework="net45" />
<package id="Rx-Core" version="2.2.5" targetFramework="net45" /> <package id="Rx-Core" version="2.2.5" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" /> <package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" />
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" /> <package id="Rx-Linq" version="2.2.5" targetFramework="net45" />
+3 -3
View File
@@ -2,7 +2,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.34014 // Runtime Version:4.0.30319.42000
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.
@@ -37,9 +37,9 @@ namespace Disco.Web.Views.Shared
[System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")]
[System.Web.WebPages.PageVirtualPathAttribute("~/Views/Shared/_Layout.cshtml")] [System.Web.WebPages.PageVirtualPathAttribute("~/Views/Shared/_Layout.cshtml")]
public partial class Layout : Disco.Services.Web.WebViewPage<dynamic> public partial class _Layout : Disco.Services.Web.WebViewPage<dynamic>
{ {
public Layout() public _Layout()
{ {
} }
public override void Execute() public override void Execute()