From 7e0c2d4b25a469cb976c5f1d10a67bf816cc2fc4 Mon Sep 17 00:00:00 2001 From: Mark Domansky <4317290+markdomansky@users.noreply.github.com> Date: Sun, 20 May 2018 20:28:43 -0500 Subject: [PATCH 1/2] updated folder structure --- WebJEA/My Project/AssemblyInfo.vb | 4 ++-- WebJEA/My Project/Settings.Designer.vb | 2 +- WebJEA/My Project/Settings.settings | 2 +- WebJEA/Web.config | 2 +- WebJEA/WebJEA.vbproj | 4 +++- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/WebJEA/My Project/AssemblyInfo.vb b/WebJEA/My Project/AssemblyInfo.vb index e809fbe..79f0789 100644 --- a/WebJEA/My Project/AssemblyInfo.vb +++ b/WebJEA/My Project/AssemblyInfo.vb @@ -30,5 +30,5 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/WebJEA/My Project/Settings.Designer.vb b/WebJEA/My Project/Settings.Designer.vb index 80e4422..da53c2a 100644 --- a/WebJEA/My Project/Settings.Designer.vb +++ b/WebJEA/My Project/Settings.Designer.vb @@ -56,7 +56,7 @@ Namespace My _ + Global.System.Configuration.DefaultSettingValueAttribute("C:\prj\WebJEA CE\WebJEATest\configlocal.json")> _ Public ReadOnly Property configfile() As String Get Return CType(Me("configfile"),String) diff --git a/WebJEA/My Project/Settings.settings b/WebJEA/My Project/Settings.settings index 9fa762f..dbc58b9 100644 --- a/WebJEA/My Project/Settings.settings +++ b/WebJEA/My Project/Settings.settings @@ -3,7 +3,7 @@ - C:\Dropbox\Scripts\VB.NET\WebJEA\WebJEATest\configlocal.json + C:\prj\WebJEA CE\WebJEATest\configlocal.json \ No newline at end of file diff --git a/WebJEA/Web.config b/WebJEA/Web.config index 4908e9b..144a982 100644 --- a/WebJEA/Web.config +++ b/WebJEA/Web.config @@ -32,7 +32,7 @@ - C:\Dropbox\Scripts\PowerShell\WebJEATest\configlocal.json + C:\prj\WebJEA CE\WebJEATest\configlocal.json diff --git a/WebJEA/WebJEA.vbproj b/WebJEA/WebJEA.vbproj index 3eb5106..289a955 100644 --- a/WebJEA/WebJEA.vbproj +++ b/WebJEA/WebJEA.vbproj @@ -27,7 +27,7 @@ - 0.9.121.18096 + 0.9.125.18132 2.7 @@ -117,6 +117,8 @@ + + From 508c6fa31de0c87c569b94d7b84377e377ba1477 Mon Sep 17 00:00:00 2001 From: Mark Domansky <4317290+markdomansky@users.noreply.github.com> Date: Sun, 20 May 2018 21:15:12 -0500 Subject: [PATCH 2/2] Added a few default values to variables. Updated Telemetry to QueueBackgroundWorkItem to improve user experience. Telemetry sends additional info, sends as json instead of CSV, but still anonymous data. --- WebJEA/Config.vb | 8 +- WebJEA/Global.asax.vb | 3 + WebJEA/Global.vb | 3 +- WebJEA/GroupFinder.vb | 2 +- WebJEA/Helpers.vb | 46 ++--------- WebJEA/My Project/AssemblyInfo.vb | 4 +- WebJEA/Telemetry.vb | 127 ++++++++++++++++++++++++++++++ WebJEA/UserInfo.vb | 2 +- WebJEA/Web.config | 3 + WebJEA/WebJEA.vbproj | 18 ++++- WebJEA/default.aspx.vb | 30 +++++-- 11 files changed, 193 insertions(+), 53 deletions(-) create mode 100644 WebJEA/Telemetry.vb diff --git a/WebJEA/Config.vb b/WebJEA/Config.vb index cf70d90..d52af02 100644 --- a/WebJEA/Config.vb +++ b/WebJEA/Config.vb @@ -146,7 +146,7 @@ Public Function GetCommand(uinfo As UserInfo, ID As String) As PSCmd - Dim foundCmd As PSCmd + Dim foundCmd As PSCmd = Nothing For Each Command As PSCmd In Commands If Command.ID = ID Then @@ -154,8 +154,10 @@ End If Next - If (IsGlobalUser(uinfo) Or foundCmd.IsCommandAvailable(uinfo)) Then - Return foundCmd + If (foundCmd IsNot Nothing) Then + If (IsGlobalUser(uinfo) Or foundCmd.IsCommandAvailable(uinfo)) Then + Return foundCmd + End If End If 'no match diff --git a/WebJEA/Global.asax.vb b/WebJEA/Global.asax.vb index 7d8fc8b..e0701d6 100644 --- a/WebJEA/Global.asax.vb +++ b/WebJEA/Global.asax.vb @@ -11,6 +11,9 @@ Public Class Global_asax Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs) ' Fires when the session is started + Session("init") = 0 'forces session to start, so session id is maintained. + Session("sessionid") = Session.SessionID + End Sub Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs) diff --git a/WebJEA/Global.vb b/WebJEA/Global.vb index dff6456..98a339f 100644 --- a/WebJEA/Global.vb +++ b/WebJEA/Global.vb @@ -4,6 +4,7 @@ Public grpfinder As New GroupFinder Public uinfo As UserInfo Public cfg As WebJEA.Config + Public objTelemetry As New Telemetry Public Enum globalKeys aws_enabled @@ -17,7 +18,7 @@ {globalKeys.aws_key, "AKIAJ6X6FWQ3UWAOQL7Q"}, {globalKeys.aws_keysec, "o2xPU115uqFZA6OotH9IWfWXGATU17wtm7vQRev6"}, {globalKeys.aws_serviceUrl, "https://sqs.us-west-2.amazonaws.com"}, - {globalKeys.aws_queueUrl, "https://sqs.us-west-2.amazonaws.com/777088161147/webjea-usage"}} + {globalKeys.aws_queueUrl, "https://sqs.us-west-2.amazonaws.com/777088161147/webjea-telemetry"}} End Module diff --git a/WebJEA/GroupFinder.vb b/WebJEA/GroupFinder.vb index 3c71547..f453966 100644 --- a/WebJEA/GroupFinder.vb +++ b/WebJEA/GroupFinder.vb @@ -33,7 +33,7 @@ Public Class GroupFinder '#End If 'create a context for the domain/machine in the group - Dim pc As PrincipalContext + Dim pc As PrincipalContext = Nothing If prvPC.ContainsKey(groupcontext) Then 'sid already found in cache dlog.Trace("GroupFinder: GetSID: Found cached Context: " & groupcontext) pc = prvPC(groupcontext) diff --git a/WebJEA/Helpers.vb b/WebJEA/Helpers.vb index 5683fc8..5d79e3f 100644 --- a/WebJEA/Helpers.vb +++ b/WebJEA/Helpers.vb @@ -1,4 +1,8 @@ -Module Helpers +Imports Microsoft.VisualBasic +Imports Microsoft.VisualBasic.Devices + + +Module Helpers Public Function CoalesceString(ByVal ParamArray arguments As String()) As String Dim argument As String For Each argument In arguments @@ -27,44 +31,7 @@ End Function - Public Sub SendUsage(DomainSid As String, DomainDNSRoot As String, ScriptID As String, UserID As String, ParamCount As Integer, isOnload As Boolean, SecondsRuntime As Single) - If globalSettings(globalKeys.aws_enabled) = False Then Return 'disable in code - - Dim wints As DateTime = DateTime.UtcNow - Dim ts As Integer = (wints - New DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds - Dim version As String = "1" 'version of string format - Dim oid As String = UsageHash(DomainSid & ";" & DomainDNSRoot.ToUpper()) - Dim sid As String = UsageHash(oid & ";" & ScriptID.ToUpper()) - Dim uid As String = UsageHash(oid & ";" & UserID.ToUpper()) - Dim isOnloadInt As Integer = Math.Abs(Convert.ToInt16(isOnload)) '0=false, 1=true - Dim secondsRuntimeStr As String = (Math.Ceiling(SecondsRuntime * 10D) / 10D).ToString() - Dim msg As String = wints.ToString("yyyy-MM-dd hh:mm:ss") & "," & ts & "," & version & "," & oid & "," & sid & "," & uid & "," & ParamCount & "," & isOnloadInt & "," & secondsRuntimeStr - - - Try - 'Dim cred As Amazon.Runtime.AWSCredentials = New Amazon.Runtime.AnonymousAWSCredentials - Dim cred As Amazon.Runtime.AWSCredentials = New Amazon.Runtime.BasicAWSCredentials(globalSettings(globalKeys.aws_key), globalSettings(globalKeys.aws_keysec)) - - Dim conf As New Amazon.SQS.AmazonSQSConfig - conf.Timeout = New TimeSpan(0, 0, 5) - conf.ServiceURL = globalSettings(globalKeys.aws_serviceUrl) - - Dim client As New Amazon.SQS.AmazonSQSClient(cred, conf) - - Dim req As New Amazon.SQS.Model.SendMessageRequest - req.QueueUrl = globalSettings(globalKeys.aws_queueUrl) - req.MessageBody = msg - - Dim resp As Amazon.SQS.Model.SendMessageResponse = client.SendMessage(req) - Catch - 'if it errors, do nothing - End Try - - - - End Sub - - Public Function UsageHash(strin As String) As String + Public Function StringHash256(strin As String) As String 'Const rounds As Integer = 5 @@ -84,6 +51,7 @@ Return ByteArrayToHexString(hash) End Function + Private Function ByteArrayToHexString(ByVal bytes_Input As Byte()) As String Dim strTemp As New StringBuilder(bytes_Input.Length * 2) For Each b As Byte In bytes_Input diff --git a/WebJEA/My Project/AssemblyInfo.vb b/WebJEA/My Project/AssemblyInfo.vb index 79f0789..f187de8 100644 --- a/WebJEA/My Project/AssemblyInfo.vb +++ b/WebJEA/My Project/AssemblyInfo.vb @@ -30,5 +30,5 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/WebJEA/Telemetry.vb b/WebJEA/Telemetry.vb new file mode 100644 index 0000000..2285a39 --- /dev/null +++ b/WebJEA/Telemetry.vb @@ -0,0 +1,127 @@ +Imports System.Web.Hosting +Imports System.Threading +Imports System.Threading.Tasks +Imports System.Web.Script.Serialization + +Class Telemetry + + Private Metrics As New Dictionary(Of String, Object) + + Public Sub Add(key As String, value As Object) + If Metrics.Keys.Contains(key) Then + Metrics(key) = value 'update + Else + Metrics.Add(key, value) 'add + End If + + End Sub + + Public Sub Clear(key As String) + Metrics.Clear() + End Sub + Public Sub Remove(key As String) + If Metrics.Keys.Contains(key) Then + Metrics.Remove(key) + End If + End Sub + Public Sub SendTelemetry() + dlog.Trace("SendTelemetry") + 'sends whatever telemetry we have + Dim cts As New CancellationTokenSource + HostingEnvironment.QueueBackgroundWorkItem(Sub() AddMetricsAndSendTelemetry()) + + End Sub + + Public Sub AddIDs(DomainSid As String, DomainDNSRoot As String, ScriptID As String, UserID As String, Optional Permitted As Boolean = True) + + Dim oid As String = StringHash256(DomainSid & ";" & DomainDNSRoot.ToUpper()) + Add("orgid", oid) + Add("scriptid", StringHash256(oid & ";" & ScriptID.ToUpper())) + Add("userid", StringHash256(oid & ";" & UserID.ToUpper())) + Add("permitted", Permitted) + + End Sub + + Public Sub AddRuntime(SecondsRuntime As Single, Optional isOnload As Boolean = False) + + Dim RuntimeName = "runtimesec" + If isOnload Then RuntimeName = "runtimesecOnload" + Add(RuntimeName, (Math.Ceiling(SecondsRuntime * 10D) / 10D).ToString()) 'round up to 1 decimal + + End Sub + + Private Sub AddMetricsAndSendTelemetry() + AddSystemMetrics() + AddTimeMetrics() + SubmitToAWSQueue(Metrics) + + End Sub + + Private Sub SubmitToAWSQueue(dictobj As Dictionary(Of String, Object)) + Dim msg As String = ToJSON(dictobj) + + SubmitToAWSQueue(msg) + End Sub + + Private Sub SubmitToAWSQueue(msg As String) + If globalSettings(globalKeys.aws_enabled) = False Then Return 'disable in code + + Try + 'Dim cred As Amazon.Runtime.AWSCredentials = New Amazon.Runtime.AnonymousAWSCredentials + dlog.Info("Sending Telemetry: " + msg) + Dim cred As Amazon.Runtime.AWSCredentials = New Amazon.Runtime.BasicAWSCredentials(globalSettings(globalKeys.aws_key), globalSettings(globalKeys.aws_keysec)) + + Dim conf As New Amazon.SQS.AmazonSQSConfig + conf.Timeout = New TimeSpan(0, 0, 5) + conf.ServiceURL = globalSettings(globalKeys.aws_serviceUrl) + + Dim client As New Amazon.SQS.AmazonSQSClient(cred, conf) + + Dim req As New Amazon.SQS.Model.SendMessageRequest + req.QueueUrl = globalSettings(globalKeys.aws_queueUrl) + req.MessageBody = msg + + Dim resp As Amazon.SQS.Model.SendMessageResponse = client.SendMessage(req) + Catch + 'if it errors, do nothing + End Try + + + + End Sub + + Private Function ToJSON(obj As Object) As String + + Dim serializer As New JavaScriptSerializer() + serializer.RecursionLimit = 2 + Return serializer.Serialize(obj) + + End Function + + Private Sub AddTimeMetrics() + + Dim wints As DateTime = DateTime.UtcNow + Dim ts As Integer = (wints - New DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds + Dim version As String = "2" 'version of string format + + Add("wints", wints.ToString("yyyy-MM-dd hh:mm:ss")) + Add("unixts", ts) + Add("version", version) + + End Sub + + + Private Sub AddSystemMetrics() + + Add("CPUCount", Environment.ProcessorCount) 'cpu count + Dim mgmtobjs As System.Management.ManagementObjectCollection = New System.Management.ManagementObjectSearcher("Select MaxClockSpeed from Win32_Processor").Get() + For Each mgmtobj In mgmtobjs + Add("CPUMhz", mgmtobj("maxclockspeed")) + Next + Add("OS", My.Computer.Info.OSFullName) 'os details + Add("RAM", Math.Round(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024 / 1024, 1)) 'GB ram + + + End Sub + +End Class diff --git a/WebJEA/UserInfo.vb b/WebJEA/UserInfo.vb index 6f92bfb..8c782f0 100644 --- a/WebJEA/UserInfo.vb +++ b/WebJEA/UserInfo.vb @@ -52,7 +52,7 @@ Public Class UserInfo Try Dim regpath As String = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" - Dim readValue = My.Computer.Registry.GetValue(regpath, "MachineGuid", "-") + Dim readValue = My.Computer.Registry.GetValue(regpath, "MachineGuid", "-") 'i think this always fails for permissions prvDomainSID = readValue prvDomainDNSRoot = Environment.MachineName Catch diff --git a/WebJEA/Web.config b/WebJEA/Web.config index 144a982..99aae9b 100644 --- a/WebJEA/Web.config +++ b/WebJEA/Web.config @@ -24,6 +24,9 @@ + diff --git a/WebJEA/WebJEA.vbproj b/WebJEA/WebJEA.vbproj index 289a955..b2aa02d 100644 --- a/WebJEA/WebJEA.vbproj +++ b/WebJEA/WebJEA.vbproj @@ -28,7 +28,8 @@ 0.9.125.18132 - 2.7 + 2.8 + true @@ -45,6 +46,19 @@ AllFilesInTheProject false false + ES2017 + None + AMD + True + False + False + + + False + True + True + + pdbonly @@ -88,6 +102,7 @@ + False ..\..\..\..\..\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll @@ -344,6 +359,7 @@ + diff --git a/WebJEA/default.aspx.vb b/WebJEA/default.aspx.vb index f0cf6f0..fce162c 100644 --- a/WebJEA/default.aspx.vb +++ b/WebJEA/default.aspx.vb @@ -2,7 +2,7 @@ Inherits System.Web.UI.Page Dim cmdid As String - 'TODO: 7- Add ParameterSet support + 'TODO: 7- Add ParameterSet support? 'advanced functions should be able to retrieve the get-help and parameter data, then permit overriding @@ -16,6 +16,9 @@ dlog = NLog.LogManager.GetCurrentClassLogger() dlog.Trace("Page: Start") + objTelemetry.Add("sessionid", StringHash256(Session.SessionID)) 'to correlate one user's activities + objTelemetry.Add("requestid", StringHash256(Guid.NewGuid().ToString())) 'to correlate multiple telemetry from the same page request + uinfo = New UserInfo Dim psweb = New PSWebHelper @@ -26,13 +29,15 @@ Dim configstr As String = GetFileContent(WebJEA.My.Settings(configid)) Try cfg = JsonConvert.DeserializeObject(Of WebJEA.Config)(configstr) + objTelemetry.Add("CommandCount", cfg.Commands.Count) + objTelemetry.Add("PermGlobalCount", cfg.PermittedGroups.Count) Catch Throw New Exception("Could not read config file") End Try 'TODO: 9 - Improve JSON read process. The current system is a hack, but it does work. - 'TODO: 5 - eventually, use a cached config, and then check for changes, and reload if appropriate + 'TODO: 5 - consider, using cached config, and then check for changes, and reload if appropriate 'parse group info Try @@ -42,6 +47,7 @@ End Try dlog.Trace("IsGlobalUser: " & cfg.IsGlobalUser(uinfo)) + objTelemetry.Add("IsGlobalUser", cfg.IsGlobalUser(uinfo)) 'determine which cmds the user has access to Dim menuitems As List(Of MenuItem) = cfg.GetMenu(uinfo) @@ -57,7 +63,6 @@ End If cfg.Init(cmdid) 'json's deserialize doesn't can't call new with parameters, so we do all the stuff we should do during the deserialize process. - 'build display page lblTitle.Text = cfg.Title @@ -76,9 +81,15 @@ Dim cmd As PSCmd = cfg.GetCommand(uinfo, cmdid) If cmd Is Nothing Then + objTelemetry.AddIDs(uinfo.DomainSID, uinfo.DomainDNSRoot, cmdid, uinfo.UserName, Permitted:=False) + divCmdBody.InnerText = "You do not have access to this command." dlog.Error("User " & uinfo.UserName & " requested cmdid " & cmdid & " that does not exist (or they don't have access to)") Else + objTelemetry.AddIDs(uinfo.DomainSID, uinfo.DomainDNSRoot, cmd.ID, uinfo.UserName) + objTelemetry.Add("PermCount", cmd.PermittedGroups.Count) + objTelemetry.Add("ParamCount", cmd.Parameters.Count) + 'build display lblCmdTitle.Text = cmd.DisplayName If cmd.Synopsis <> "" Then @@ -105,7 +116,7 @@ ps.Script = cmd.OnloadScript ps.LogParameters = cmd.LogParameters ps.Run() - If cfg.SendTelemetry Then SendUsage(uinfo.DomainSID, uinfo.DomainDNSRoot, cmd.ID, uinfo.UserName, cmd.Parameters.Count, True, ps.Runtime) + objTelemetry.AddRuntime(ps.Runtime, isOnload:=True) consoleOnload.InnerHtml = psweb.ConvertToHTML(ps.getOutputData) ps = Nothing @@ -159,9 +170,10 @@ ps.Script = cmd.Script ps.LogParameters = cmd.LogParameters ps.Parameters = psweb.getParameters(cmd, Page) + objTelemetry.Add("ParamUsed", ps.Parameters.Count) ps.Run() - If cfg.SendTelemetry Then SendUsage(uinfo.DomainSID, uinfo.DomainDNSRoot, cmd.ID, uinfo.UserName, cmd.Parameters.Count, False, ps.Runtime) + objTelemetry.AddRuntime(ps.Runtime) consoleOutput.Text = psweb.ConvertToHTML(ps.getOutputData) ps = Nothing @@ -183,5 +195,13 @@ End Function + Private Sub _default_Init(sender As Object, e As EventArgs) Handles Me.Init + ViewStateUserKey = Session.SessionID + End Sub + Private Sub _default_LoadComplete(sender As Object, e As EventArgs) Handles Me.LoadComplete + If cfg.SendTelemetry Then + objTelemetry.SendTelemetry() + End If + End Sub End Class \ No newline at end of file