diff --git a/DesktopEdge/MainWindow.xaml.cs b/DesktopEdge/MainWindow.xaml.cs index 387f0df69..31a208494 100644 --- a/DesktopEdge/MainWindow.xaml.cs +++ b/DesktopEdge/MainWindow.xaml.cs @@ -252,8 +252,11 @@ private void DoClose(bool isComplete) { if (IdentityMenu.IsVisible) { if (isComplete) { if (MFASetup.Type == 2) { - if (IdentityMenu.Identity.MFAInfo.RecoveryCodes.Length==0) ShowBlurbAsync("You do not have anymore recovery codes", this.RECOVER); - else ShowRecovery(IdentityMenu.Identity); + if (IdentityMenu.Identity.MFAInfo?.RecoveryCodes?.Length > 0) { + ShowRecovery(IdentityMenu.Identity); + } else { + ShowBlurbAsync("You do not have anymore recovery codes", this.RECOVER); + } } else if (MFASetup.Type == 3) { IdentityMenu.Identity.IsMFAEnabled = false; IdentityMenu.Identity.MFAInfo.IsAuthenticated = false; @@ -484,6 +487,7 @@ async private void MainWindow_Loaded(object sender, RoutedEventArgs e) { serviceClient.OnServiceEvent += ServiceClient_OnServiceEvent; serviceClient.OnTunnelStatusEvent += ServiceClient_OnTunnelStatusEvent; serviceClient.OnMfaEvent += ServiceClient_OnMfaEvent; + serviceClient.OnLogLevelEvent += ServiceClient_OnLogLevelEvent; Application.Current.Properties.Add("ServiceClient", serviceClient); monitorClient = new MonitorClient(); @@ -721,6 +725,7 @@ private void ServiceClient_OnIdentityEvent(object sender, IdentityEvent e) { found.Name = zid.Name; found.ControllerUrl = zid.ControllerUrl; found.IsEnabled = zid.IsEnabled; + LoadIdentities(true); return; } } else if (e.Action == "updated") { @@ -823,6 +828,20 @@ private void ServiceClient_OnTunnelStatusEvent(object sender, TunnelStatusEvent } }); } + + private void ServiceClient_OnLogLevelEvent(object sender, LogLevelEvent e) { +            if (e.LogLevel != null) { +                SetLogLevel_monitor(e.LogLevel); +                this.Dispatcher.Invoke(() =>  { +                    this.MainMenu.LogLevel = e.LogLevel; +                    Ziti.Desktop.Edge.Utils.UIUtils.SetLogLevel(e.LogLevel); +                }); +            } +        } + +        async private void SetLogLevel_monitor(string loglevel)  { +            await monitorClient.SetLogLevelAsync(loglevel); +        } private void IdentityForgotten(ZitiIdentity forgotten) { ZitiIdentity idToRemove = null; diff --git a/DesktopEdge/Views/Screens/IdentityDetails.xaml.cs b/DesktopEdge/Views/Screens/IdentityDetails.xaml.cs index f7fd423d7..f15ce0658 100644 --- a/DesktopEdge/Views/Screens/IdentityDetails.xaml.cs +++ b/DesktopEdge/Views/Screens/IdentityDetails.xaml.cs @@ -119,7 +119,7 @@ public void UpdateView() { ServiceList.Children.Clear(); if (_identity.Services.Count>0) { foreach(var zitiSvc in _identity.Services.OrderBy(s => s.Name.ToLower())) { - if (zitiSvc.Name.ToLower().IndexOf(filter)>=0||zitiSvc.ToString().ToLower().IndexOf(filter)>=0) { + if (zitiSvc.Name.ToLower().IndexOf(filter.ToLower()) >=0||zitiSvc.ToString().ToLower().IndexOf(filter.ToLower()) >=0) { Logger.Trace("painting: " + zitiSvc.Name); ServiceInfo info = new ServiceInfo(); info.Info = zitiSvc; diff --git a/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs index d82cccb80..2d9ade78a 100644 --- a/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs +++ b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs @@ -418,6 +418,12 @@ public class IdentityEvent : ActionEvent { public Identity Id { get; set; } } + + public class LogLevelEvent : ActionEvent + { + public string LogLevel { get; set; } + } + public class MonitorServiceStatusEvent : SvcResponse { public string Status { get; set; } diff --git a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs index 554a0e54b..8e628be64 100644 --- a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs +++ b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs @@ -33,6 +33,7 @@ public class DataClient : AbstractClient { public event EventHandler> OnMetricsEvent; public event EventHandler OnIdentityEvent; public event EventHandler OnServiceEvent; + public event EventHandler OnLogLevelEvent; public event EventHandler OnMfaEvent; protected override void ShutdownEvent(StatusEvent e) { @@ -56,6 +57,11 @@ protected virtual void IdentityEvent(IdentityEvent e) { protected virtual void ServiceEvent(ServiceEvent e) { OnServiceEvent?.Invoke(this, e); } + + protected virtual void LogLevelEvent(LogLevelEvent e) { + OnLogLevelEvent?.Invoke(this, e); + } + protected virtual void MfaEvent(MfaEvent e) { OnMfaEvent?.Invoke(this, e); @@ -345,6 +351,13 @@ protected override void ProcessLine(string line) { ServiceEvent(svc); } break; + case "logLevel": + LogLevelEvent ll = serializer.Deserialize(jsonReader); + + if (ll != null) { + LogLevelEvent(ll); + } + break; case "shutdown": Logger.Debug("shutdown message received"); var se = new StatusEvent(); diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs index 5b6781074..57fdf0301 100644 --- a/ZitiUpdateService/UpdateService.cs +++ b/ZitiUpdateService/UpdateService.cs @@ -199,6 +199,7 @@ private string CaptureLogs() { outputTasklist(destinationLocation); outputRouteInfo(destinationLocation); outputNetstatInfo(destinationLocation); + outputNrpt(destinationLocation); Task.Delay(500).Wait(); @@ -340,6 +341,37 @@ private void outputNetstatInfo(string destinationFolder) { } } + private void outputNrpt(string destinationFolder) { + Logger.Info("outputting NRPT rules"); + try { + Logger.Info("outputting NRPT DnsClientNrptRule"); + string nrptRuleOutput = Path.Combine(destinationFolder, "NrptRule.txt"); + Process nrptRuleProcess = new Process(); + ProcessStartInfo nrptRuleStartInfo = new ProcessStartInfo(); + nrptRuleStartInfo.WindowStyle = ProcessWindowStyle.Hidden; + nrptRuleStartInfo.FileName = "cmd.exe"; + nrptRuleStartInfo.Arguments = $"/C powershell \"Get-DnsClientNrptRule | sort -Property Namespace\" > \"{nrptRuleOutput}\""; + Logger.Info("Running: {0}", nrptRuleStartInfo.Arguments); + nrptRuleProcess.StartInfo = nrptRuleStartInfo; + nrptRuleProcess.Start(); + nrptRuleProcess.WaitForExit(); + + Logger.Info("outputting NRPT DnsClientNrptPolicy"); + string nrptOutput = Path.Combine(destinationFolder, "NrptPolicy.txt"); + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = "cmd.exe"; + startInfo.Arguments = $"/C powershell \"Get-DnsClientNrptPolicy | sort -Property Namespace\" > \"{nrptOutput}\""; + Logger.Info("Running: {0}", startInfo.Arguments); + process.StartInfo = startInfo; + process.Start(); + process.WaitForExit(); + } catch (Exception ex) { + Logger.Error(ex, "Unexpected error {0}", ex.Message); + } + } + public void Debug() { OnStart(null);// new string[] { "FilesystemCheck" }); } diff --git a/release-notes.md b/release-notes.md index 065de9772..d291aec51 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,19 @@ +# Release 1.9.2 + +## What's New +* [#322](https://github.com/openziti/desktop-edge-win/issues/322) Ability to toggle identity, set loglevel and generate feedback zip file from cmd line + +## Other changes: +* none + +## Bugs fixed: +* [#346](https://github.com/openziti/desktop-edge-win/issues/346) Fixed the UI filtering of services on the Identity detail screen +* [#348](https://github.com/openziti/desktop-edge-win/issues/348) IP addresses do not need to be added to the NRPT +* [#349](https://github.com/openziti/desktop-edge-win/issues/349) Too many services can cause the NRPT update to fail + +## Dependency Updates +* none + # Release 1.9.1 ## What's New diff --git a/service/.gitignore b/service/.gitignore index da9d61609..879e24576 100644 --- a/service/.gitignore +++ b/service/.gitignore @@ -7,3 +7,4 @@ NFWintunInstaller.msi wintun.dll gdb.txt +logs diff --git a/service/cziti/ctun.go b/service/cziti/ctun.go index 4a700e906..09c082cd0 100644 --- a/service/cziti/ctun.go +++ b/service/cziti/ctun.go @@ -321,4 +321,4 @@ Set-NetIPInterface -InterfaceIndex $i.ifIndex -InterfaceMetric %d`, interfaceNam if err != nil { log.Errorf("ERROR setting interface metric: %v", err) } -} \ No newline at end of file +} diff --git a/service/cziti/mfa.go b/service/cziti/mfa.go index bf8ddcc0a..ee8ab0363 100644 --- a/service/cziti/mfa.go +++ b/service/cziti/mfa.go @@ -113,9 +113,9 @@ func ziti_mfa_cb_verify_go(_ C.ziti_context, status C.int, cFingerprint *C.char) fp := C.GoString(cFingerprint) log.Debugf("ziti_mfa_cb_verify_go called for %s. status: %d for ", fp, int(status)) var m = dto.MfaEvent{ - ActionEvent: dto.MFAEnrollmentVerificationEvent, - Fingerprint: fp, - Successful: false, + ActionEvent: dto.MFAEnrollmentVerificationEvent, + Fingerprint: fp, + Successful: false, RecoveryCodes: nil, } @@ -135,6 +135,7 @@ func ziti_mfa_cb_verify_go(_ C.ziti_context, status C.int, cFingerprint *C.char) } var rtnCodes = make(chan mfaCodes) + func ReturnMfaCodes(id *ZIdentity, code string) ([]string, error) { ccode := C.CString(code) defer C.free(unsafe.Pointer(ccode)) @@ -179,6 +180,7 @@ func ziti_mfa_recovery_codes_cb_return(_ C.ziti_context, status C.int, recoveryC } var genCodes = make(chan mfaCodes) + func GenerateMfaCodes(id *ZIdentity, code string) ([]string, error) { ccode := C.CString(code) defer C.free(unsafe.Pointer(ccode)) @@ -292,9 +294,9 @@ func ziti_mfa_cb_remove_go(_ C.ziti_context, status C.int, cFingerprint *C.char) log.Debugf("ziti_mfa_cb_remove_go called for %s. status: %d for ", fp, int(status)) var m = dto.MfaEvent{ - ActionEvent: dto.MFAEnrollmentRemovedEvent, - Fingerprint: fp, - Successful: false, + ActionEvent: dto.MFAEnrollmentRemovedEvent, + Fingerprint: fp, + Successful: false, RecoveryCodes: nil, } @@ -311,4 +313,4 @@ func ziti_mfa_cb_remove_go(_ C.ziti_context, status C.int, cFingerprint *C.char) log.Debugf("sending ziti_mfa_verify response back to UI for %s. verified: %t. error: %s", fp, m.Successful, m.Error) goapi.BroadcastEvent(m) -} \ No newline at end of file +} diff --git a/service/cziti/sdk.go b/service/cziti/sdk.go index 975fac586..28b01c73c 100644 --- a/service/cziti/sdk.go +++ b/service/cziti/sdk.go @@ -340,7 +340,7 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, z } zid.Services.Store(svcId, &added) - ServiceChanges <- se + go func() {ServiceChanges <- se}() //do this in a go routine just in case it gets blocked so the uv loop doesn't block } return addresses } @@ -411,7 +411,9 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) { } addys := serviceCB(ztx, removed, C.ZITI_SERVICE_UNAVAILABLE, zid) for _, toRemove := range addys { - hostnamesToRemove[toRemove.HostName] = true + if toRemove.IsHost { + hostnamesToRemove[toRemove.HostName] = true + } } } for i := 0; true; i++ { @@ -422,12 +424,16 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) { log.Info("service changed remove the service then add it back immediately", C.GoString(changed.name)) addys := serviceCB(ztx, changed, C.ZITI_SERVICE_UNAVAILABLE, zid) for _, toRemove := range addys { - hostnamesToRemove[toRemove.HostName] = true + if toRemove.IsHost { + hostnamesToRemove[toRemove.HostName] = true + } } addys = serviceCB(ztx, changed, C.ZITI_OK, zid) for _, toAdd := range addys { - hostnamesToAdd[toAdd.HostName] = true + if toAdd.IsHost { + hostnamesToAdd[toAdd.HostName] = true + } } } for i := 0; true; i++ { @@ -437,7 +443,9 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) { } addys := serviceCB(ztx, added, C.ZITI_OK, zid) for _, toAdd := range addys { - hostnamesToAdd[toAdd.HostName] = true + if toAdd.IsHost { + hostnamesToAdd[toAdd.HostName] = true + } } } @@ -453,7 +461,7 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) { var m = dto.IdentityEvent{ ActionEvent: dto.IdentityUpdateComplete, - Id: dto.Identity{ + Id: dto.Identity{ FingerPrint: zid.Fingerprint, }, } diff --git a/service/windns/windns.go b/service/windns/windns.go index f342f845a..54dd0cbf8 100644 --- a/service/windns/windns.go +++ b/service/windns/windns.go @@ -121,11 +121,35 @@ func AddNrptRules(domainsToMap map[string]bool, dnsServer string) { log.Debug("no domains to map specified to AddNrptRules. exiting early") return } + + blockSize := 50 + if len(domainsToMap) > blockSize { + log.Debugf("domainsToMap is too long [%d] and may fail on some systems. splitting the requested entries into blocks of %d", len(domainsToMap), blockSize) + } + currentSize := 0 + hostnames := make([]string, blockSize) + for hostname := range domainsToMap { + if currentSize >= blockSize { + log.Debugf("sending chunk of domains to be added to NRPT") + chunkedAddNrptRules(hostnames, dnsServer) + hostnames = make([]string, blockSize) + currentSize = 0 + } + hostnames[currentSize] = hostname + currentSize++ + } + if currentSize > 0 { + //means there's a chunk still to add.... + chunkedAddNrptRules(hostnames[:currentSize], dnsServer) + } +} + +func chunkedAddNrptRules(domainsToAdd []string, dnsServer string) { sb := strings.Builder{} sb.WriteString(`$Rules = @( `) - for hostname := range domainsToMap { + for _, hostname := range domainsToAdd { sb.WriteString(fmt.Sprintf(`@{ Namespace ="%s"; NameServers = @("%s"); Comment = "Added by ziti-tunnel"; DisplayName = "ziti-tunnel:%s"; }%s`, hostname, dnsServer, hostname, "\n")) } @@ -136,7 +160,7 @@ ForEach ($Rule in $Rules) { }`)) script := sb.String() - log.Debugf("Executing NRPT script:\n%s", script) + log.Debugf("Executing NRPT script containing %d domains:\n%s", len(domainsToAdd), script) cmd := exec.Command("powershell", "-Command", script) cmd.Stderr = os.Stdout diff --git a/service/ziti-tunnel/cli/cmdlineClientFunctions.go b/service/ziti-tunnel/cli/cmdlineClientFunctions.go deleted file mode 100644 index 59f54fe7c..000000000 --- a/service/ziti-tunnel/cli/cmdlineClientFunctions.go +++ /dev/null @@ -1,133 +0,0 @@ -package cli - -/* - * Copyright NetFoundry, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -import ( - "bufio" - "encoding/json" - "io" - "net" - "strings" - "time" - - "github.com/Microsoft/go-winio" - "github.com/openziti/desktop-edge-win/service/ziti-tunnel/dto" - "github.com/openziti/desktop-edge-win/service/ziti-tunnel/service" -) - -type fetchFromRTS func([]string, *dto.TunnelStatus, map[string]bool) dto.Response - -func sendMessagetoPipe(ipcPipeConn net.Conn, commandMsg *dto.CommandMsg, args []string) error { - writer := bufio.NewWriter(ipcPipeConn) - enc := json.NewEncoder(writer) - - err := enc.Encode(commandMsg) - if err != nil { - log.Errorf("could not encode or writer list identities message, %v", err) - return err - } - - log.Debug("Message sent to ipc pipe") - - writer.Flush() - - return nil -} - -func readMessageFromPipe(ipcPipeConn net.Conn, readDone chan struct{}, fn fetchFromRTS, args []string, flags map[string]bool) { - - reader := bufio.NewReader(ipcPipeConn) - msg, err := reader.ReadString('\n') - - if err != nil { - log.Error(err) - return - } - - if len(args) == 0 { - args = append(args, "all") - } - - dec := json.NewDecoder(strings.NewReader(msg)) - var tunnelStatus dto.ZitiTunnelStatus - if err := dec.Decode(&tunnelStatus); err == io.EOF { - return - } else if err != nil { - log.Fatal(err) - } - - if tunnelStatus.Status != nil { - responseMsg := fn(args, tunnelStatus.Status, flags) - if responseMsg.Code == service.SUCCESS { - log.Info("\n" + responseMsg.Payload.(string) + "\n" + responseMsg.Message) - } else { - log.Info(responseMsg.Error) - } - } else { - log.Errorf("Ziti tunnel retuned nil status") - } - - readDone <- struct{}{} - return -} - -//GetIdentities is to fetch identities through cmdline -func GetIdentities(args []string, flags map[string]bool) { - getDataFromIpcPipe(&GET_STATUS, GetIdentitiesFromRTS, args, flags) -} - -//GetServices is to fetch services through cmdline -func GetServices(args []string, flags map[string]bool) { - getDataFromIpcPipe(&GET_STATUS, GetServicesFromRTS, args, flags) -} - -func getDataFromIpcPipe(commandMsg *dto.CommandMsg, fn fetchFromRTS, args []string, flags map[string]bool) { - log.Infof("fetching identities through cmdline...%s", args) - - log.Debug("Connecting to pipe") - timeout := 2000 * time.Millisecond - ipcPipeConn, err := winio.DialPipe(service.IpcPipeName(), &timeout) - defer closeConn(ipcPipeConn) - - if err != nil { - log.Errorf("Connection to ipc pipe is not established, %v", err) - return - } - readDone := make(chan struct{}) - defer close(readDone) // ensure that goroutine exits - - go readMessageFromPipe(ipcPipeConn, readDone, fn, args, flags) - - err = sendMessagetoPipe(ipcPipeConn, commandMsg, args) - if err != nil { - log.Errorf("Message is not sent to ipc pipe, %v", err) - return - } - - log.Debugf("Connection to ipc pipe is established - %s and remote address %s", ipcPipeConn.LocalAddr().String(), ipcPipeConn.RemoteAddr().String()) - - <-readDone - log.Debug("read finished normally") -} - -func closeConn(conn net.Conn) { - err := conn.Close() - if err != nil { - log.Warnf("abnormal error while closing connection. %v", err) - } -} diff --git a/service/ziti-tunnel/cli/cmdlineConstants.go b/service/ziti-tunnel/cli/constants.go similarity index 70% rename from service/ziti-tunnel/cli/cmdlineConstants.go rename to service/ziti-tunnel/cli/constants.go index b7f1652cf..3c21a278a 100644 --- a/service/ziti-tunnel/cli/cmdlineConstants.go +++ b/service/ziti-tunnel/cli/constants.go @@ -1,37 +1,55 @@ -package cli - -/* - * Copyright NetFoundry, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -import ( - "github.com/openziti/desktop-edge-win/service/ziti-tunnel/dto" - "github.com/openziti/desktop-edge-win/service/ziti-tunnel/util/logging" -) - -var GET_STATUS = dto.CommandMsg{ - Function: "Status", -} - -var templateIdentity = `{{printf "%40s" "Name"}} | {{printf "%41s" "FingerPrint"}} | {{printf "%6s" "Active"}} | {{printf "%30s" "Config"}} | {{"Status"}} -{{range .}}{{printf "%40s" .Name}} | {{printf "%41s" .FingerPrint}} | {{printf "%6t" .Active}} | {{printf "%30s" .Config}} | {{.Status}} -{{end}}` - -var templateService = `{{printf "%40s" "Name"}} | {{printf "%15s" "Id"}} | {{printf "%40s" "InterceptHost"}} | {{printf "%14s" "InterceptPort"}} | {{printf "%15s" "AssignedIP"}} | {{printf "%15s" "AssignedHost"}} | {{"OwnsIntercept"}} -{{range .}}{{printf "%40s" .Name}} | {{printf "%15s" .Id}} | {{printf "%40s" .InterceptHost}} | {{printf "%14d" .InterceptPort}} | {{printf "%15s" .AssignedIP}} | {{printf "%15s" .AssignedHost}} | {{.OwnsIntercept}} -{{end}}` - -var log = logging.Logger() +package cli + +/* + * Copyright NetFoundry, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import ( + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/dto" + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/util/logging" +) + +var GET_STATUS = dto.CommandMsg{ + Function: "Status", +} + +var ONOFF_IDENTITY = dto.CommandMsg{ + Function: "IdentityOnOff", +} + +var SET_LOGLEVEL = dto.CommandMsg{ + Function: "SetLogLevel", +} + +var NOTIFY_LOGLEVEL_UI_MONITOR = dto.CommandMsg{ + Function: "NotifyLogLevelUIAndUpdateService", +} + +var NOTIFY_IDENTITY_UI = dto.CommandMsg{ + Function: "NotifyIdentityUI", +} + +var monitorIpcPipe = `\\.\pipe\OpenZiti\ziti-monitor\ipc` + +var templateIdentity = `{{printf "%40s" "Name"}} | {{printf "%41s" "FingerPrint"}} | {{printf "%6s" "Active"}} | {{printf "%30s" "Config"}} | {{"Status"}} +{{range .}}{{printf "%40s" .Name}} | {{printf "%41s" .FingerPrint}} | {{printf "%6t" .Active}} | {{printf "%30s" .Config}} | {{.Status}} +{{end}}` + +var templateService = `{{printf "%40s" "Name"}} | {{printf "%15s" "Id"}} | {{printf "%9s" "Protocols"}} | {{printf "%14s" "Ports"}} | {{printf "%60s" "Addresses"}} +{{range .}}{{printf "%40s" .Name}} | {{printf "%15s" .Id}} | {{printf "%9s" .Protocols}} | {{printf "%14s" .Ports}} | {{printf "%60s" .Addresses}} +{{end}}` + +var log = logging.Logger() diff --git a/service/ziti-tunnel/cli/ipcClient.go b/service/ziti-tunnel/cli/ipcClient.go new file mode 100644 index 000000000..c2316fe3a --- /dev/null +++ b/service/ziti-tunnel/cli/ipcClient.go @@ -0,0 +1,260 @@ +package cli + +/* + * Copyright NetFoundry, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import ( + "fmt" + "bufio" + "encoding/json" + "io" + "net" + "strings" + "time" + + "github.com/Microsoft/go-winio" + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/dto" + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/service" +) + +type fetchStatusFromRTS func([]string, *dto.TunnelStatus, map[string]bool) dto.Response +type fetchResponseFromRTS func([]string, dto.Response, map[string]bool) dto.Response + +func sendMessagetoPipe(ipcPipeConn net.Conn, commandMsg *dto.CommandMsg, args []string) error { + writer := bufio.NewWriter(ipcPipeConn) + enc := json.NewEncoder(writer) + + err := enc.Encode(commandMsg) + if err != nil { + log.Error("could not encode or writer list identities message, %v", err) + return err + } + + log.Debug("Message sent to ipc pipe") + + writer.Flush() + + return nil +} + +func readMessageFromPipe(ipcPipeConn net.Conn, readDone chan bool, fn fetchStatusFromRTS, responseFn fetchResponseFromRTS, args []string, flags map[string]bool) { + + reader := bufio.NewReader(ipcPipeConn) + msg, err := reader.ReadString('\n') + + if err != nil { + log.Error(err) + readDone <- false + return + } + + if len(args) == 0 { + args = append(args, "all") + } + + dec := json.NewDecoder(strings.NewReader(msg)) + + if fn != nil { + var tunnelStatus dto.ZitiTunnelStatus + if err := dec.Decode(&tunnelStatus); err == io.EOF { + readDone <- false + return + } else if err != nil { + log.Fatal(err) + readDone <- false + return + } + + if tunnelStatus.Status != nil { + responseMsg := fn(args, tunnelStatus.Status, flags) + if responseMsg.Code == service.SUCCESS { + fmt.Println(responseMsg.Payload.(string)) + fmt.Println(responseMsg.Message) + readDone <- true + return + } else { + if responseMsg.Error != "" { + log.Error(responseMsg.Error) + } else { + log.Info(responseMsg.Message) + } + } + } else { + log.Errorf("Ziti tunnel retuned status, %v", tunnelStatus) + } + } + + if responseFn != nil { + var response dto.Response + if err := dec.Decode(&response); err == io.EOF { + readDone <- false + return + } else if err != nil { + log.Fatal(err) + readDone <- false + return + } + + if response.Message != "" { + responseMsg := responseFn(args, response, flags) + if responseMsg.Code == service.SUCCESS { + if responseMsg.Payload != nil { + log.Infof("Payload : %v", responseMsg.Payload) + } + log.Infof("Message : %s", responseMsg.Message) + readDone <- true + return + } else { + if responseMsg.Error != "" { + log.Error(responseMsg.Error) + } else { + log.Info(responseMsg.Message) + } + } + } else { + log.Errorf("Ziti tunnel retuned nil response message, %v", response) + } + } + + readDone <- false + return +} + +func GetDataFromIpcPipe(commandMsg *dto.CommandMsg, fn fetchStatusFromRTS, responseFn fetchResponseFromRTS, args []string, flags map[string]bool) bool { + log.Infof("Command %s with args %s", commandMsg.Function, args) + + log.Debug("Connecting to pipe") + timeout := 2000 * time.Millisecond + ipcPipeConn, err := winio.DialPipe(service.IpcPipeName(), &timeout) + defer closeConn(ipcPipeConn) + + if err != nil { + log.Errorf("Connection to ipc pipe is not established, %v", err) + log.Fatal("Ziti Desktop Edge app may not be running") + return false + } + readDone := make(chan bool) + defer close(readDone) // ensure that goroutine exits + + go readMessageFromPipe(ipcPipeConn, readDone, fn, responseFn, args, flags) + + err = sendMessagetoPipe(ipcPipeConn, commandMsg, args) + if err != nil { + log.Errorf("Message is not sent to ipc pipe, %v", err) + return false + } + + log.Debugf("Connection to ipc pipe is established - %s and remote address %s", ipcPipeConn.LocalAddr().String(), ipcPipeConn.RemoteAddr().String()) + + status := <-readDone + log.Debug("read finished normally") + return status +} + +// monitor ipc messages + +func GetDataFromMonitorIpcPipe(actionMessage *dto.ActionEvent, args []string, flags map[string]bool) bool { + log.Infof("Command %s with args %s", actionMessage.StatusEvent.Op, args) + + log.Debug("Connecting to pipe") + timeout := 2000 * time.Millisecond + ipcPipeConn, err := winio.DialPipe(monitorIpcPipe, &timeout) + defer closeConn(ipcPipeConn) + + if err != nil { + log.Errorf("Connection to monitor ipc pipe is not established, %v", err) + log.Errorf("Ziti Update service may not be running") + return false + } + readDone := make(chan bool) + defer close(readDone) // ensure that goroutine exits + + go readMessageFromMonitorPipe(ipcPipeConn, readDone, args, flags) + + err = sendMessageToMonitorPipe(ipcPipeConn, actionMessage, args) + if err != nil { + log.Errorf("Message is not sent to monitor ipc pipe, %v", err) + return false + } + + log.Debugf("Connection to monitor ipc pipe is established - %s and remote address %s", ipcPipeConn.LocalAddr().String(), ipcPipeConn.RemoteAddr().String()) + + status := <-readDone + log.Debug("read finished normally") + return status +} + +func sendMessageToMonitorPipe(ipcPipeConn net.Conn, actionMessage *dto.ActionEvent, args []string) error { + writer := bufio.NewWriter(ipcPipeConn) + enc := json.NewEncoder(writer) + + err := enc.Encode(actionMessage) + if err != nil { + log.Error("could not encode or write response message, %v", err) + return err + } + + log.Debug("Message sent to monitor ipc pipe") + + writer.Flush() + + return nil +} + +func readMessageFromMonitorPipe(ipcPipeConn net.Conn, readDone chan bool, args []string, flags map[string]bool) { + + reader := bufio.NewReader(ipcPipeConn) + msg, err := reader.ReadString('\n') + + if err != nil { + log.Error(err) + readDone <- false + return + } + + dec := json.NewDecoder(strings.NewReader(msg)) + + var monitorServiceResponse dto.MonitorServiceResponse + if err := dec.Decode(&monitorServiceResponse); err == io.EOF { + readDone <- false + return + } else if err != nil { + log.Fatal(err) + readDone <- false + return + } + + if monitorServiceResponse.Code == service.SUCCESS { + log.Infof("Feedback file %s is created", monitorServiceResponse.Message) + readDone <- true + return + } else { + log.Error(monitorServiceResponse.Error) + } + + readDone <- false + return +} + +func closeConn(conn net.Conn) { + if conn != nil { + err := conn.Close() + if err != nil { + log.Warnf("abnormal error while closing connection. %v", err) + } + } +} diff --git a/service/ziti-tunnel/cli/cmdlineParseFunctions.go b/service/ziti-tunnel/cli/responseGenerator.go similarity index 72% rename from service/ziti-tunnel/cli/cmdlineParseFunctions.go rename to service/ziti-tunnel/cli/responseGenerator.go index 2da72cd30..f0143d26e 100644 --- a/service/ziti-tunnel/cli/cmdlineParseFunctions.go +++ b/service/ziti-tunnel/cli/responseGenerator.go @@ -39,15 +39,29 @@ func convertToIdentityCli(id *dto.Identity) dto.IdentityCli { } func convertToServiceCli(svc dto.Service) dto.ServiceCli { + cliPorts := "" + for _, val := range svc.Ports { + if len(cliPorts) != 0 { + cliPorts = fmt.Sprintf("%s, %d-%d", cliPorts, val.Low, val.High) + } else { + cliPorts = fmt.Sprintf("%d-%d", val.Low, val.High) + } + } + cliAddresses := "" + for _, val := range svc.Addresses { + if len(cliAddresses) != 0 { + cliAddresses = cliAddresses + ", " + val.HostName + "/" + val.IP + } else { + cliAddresses = val.HostName + "/" + val.IP + } + } + return dto.ServiceCli{ - Name: svc.Name, - Id: svc.Id, - /*InterceptHost: svc.InterceptHost, - InterceptPort: svc.InterceptPort, - AssignedIP: svc.AssignedIP, - AssignedHost: svc.AssignedHost, - */ - OwnsIntercept: svc.OwnsIntercept, + Name: svc.Name, + Id: svc.Id, + Protocols: strings.Join(svc.Protocols, ","), + Ports: cliPorts, + Addresses: cliAddresses, } } @@ -180,3 +194,35 @@ func generateResponse(dataType string, message string, filteredData interface{}, return dto.Response{Message: message, Code: service.SUCCESS, Error: "", Payload: responseStr} } + +func GetLogLevelFromRTS(args []string, status *dto.TunnelStatus, flags map[string]bool) dto.Response { + + if flags["query"] == true { + message := fmt.Sprintf("Loglevel is currently set to %s", status.LogLevel) + return dto.Response{Message: message, Code: service.SUCCESS, Error: "", Payload: ""} + } + errMsg := fmt.Sprintf("Unknown error: args %s flag %v", args, flags) + return dto.Response{Message: "", Code: service.ERROR, Error: errMsg, Payload: ""} + +} + +// GetIdentityResponseObjectFromRTS is to get identity info from the RTS +func GetIdentityResponseObjectFromRTS(args []string, status dto.Response, flags map[string]bool) dto.Response { + log.Debugf("Message from ziti-tunnel : %v", status.Message) + if status.Error == "" && status.Payload != nil { + log.Debugf("Payload from RTS %v", status.Payload) + payloadData := status.Payload.(map[string]interface{}) + identityStatus := make(map[string]interface{}) + identityStatus["FingerPrint"] = payloadData["FingerPrint"] + identityStatus["Active"] = payloadData["Active"] + identityStatus["Name"] = payloadData["Name"] + return dto.Response{Message: status.Message, Code: service.SUCCESS, Error: "", Payload: identityStatus} + } else { + return status + } +} + +// GetResponseObjectFromRTS is to get response object info from the RTS +func GetResponseObjectFromRTS(args []string, status dto.Response, flags map[string]bool) dto.Response { + return status +} diff --git a/service/ziti-tunnel/cli/service.go b/service/ziti-tunnel/cli/service.go new file mode 100644 index 000000000..ae695c888 --- /dev/null +++ b/service/ziti-tunnel/cli/service.go @@ -0,0 +1,55 @@ +package cli + +import ( + "strings" + + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/dto" +) + +//GetIdentities is to fetch identities through cmdline +func GetIdentities(args []string, flags map[string]bool) { + GetDataFromIpcPipe(&GET_STATUS, GetIdentitiesFromRTS, nil, args, flags) +} + +//GetServices is to fetch services through cmdline +func GetServices(args []string, flags map[string]bool) { + GetDataFromIpcPipe(&GET_STATUS, GetServicesFromRTS, nil, args, flags) +} + +//OnOffIdentity is to enable or disable the identity through cmdline +func OnOffIdentity(args []string, flags map[string]bool) { + identityPayload := make(map[string]interface{}) + identityPayload["OnOff"] = strings.EqualFold(args[1], "on") + identityPayload["Fingerprint"] = args[0] + ONOFF_IDENTITY.Payload = identityPayload + log.Debugf("OnOffIdentity Payload %v", ONOFF_IDENTITY) + status := GetDataFromIpcPipe(&ONOFF_IDENTITY, nil, GetIdentityResponseObjectFromRTS, args, flags) + if status { + NOTIFY_IDENTITY_UI.Payload = identityPayload + log.Infof("Notifying the Identity Status to UI %v", identityPayload) + GetDataFromIpcPipe(&NOTIFY_IDENTITY_UI, nil, GetResponseObjectFromRTS, args, flags) + } +} + +//SetLogLevel is to change the loglevel through cmdline +func SetLogLevel(args []string, flags map[string]bool) { + if flags["query"] == true { + GetDataFromIpcPipe(&GET_STATUS, GetLogLevelFromRTS, nil, args, flags) + } else { + loglevelPayload := make(map[string]interface{}) + loglevelPayload["Level"] = args[0] + SET_LOGLEVEL.Payload = loglevelPayload + log.Debugf("LogLevel Payload %v", SET_LOGLEVEL) + status := GetDataFromIpcPipe(&SET_LOGLEVEL, nil, GetResponseObjectFromRTS, args, flags) + if status { + NOTIFY_LOGLEVEL_UI_MONITOR.Payload = loglevelPayload + log.Infof("Notifying the LogLevel to UI and Ziti monitor service %v", loglevelPayload) + GetDataFromIpcPipe(&NOTIFY_LOGLEVEL_UI_MONITOR, nil, GetResponseObjectFromRTS, args, flags) + } + } +} + +//GetFeedback is to create logs zip through cmdline +func GetFeedback(args []string, flags map[string]bool) { + GetDataFromMonitorIpcPipe(&dto.FEEDBACK_REQUEST, args, flags) +} diff --git a/service/ziti-tunnel/cmd/feedback.go b/service/ziti-tunnel/cmd/feedback.go new file mode 100644 index 000000000..a1e2e9653 --- /dev/null +++ b/service/ziti-tunnel/cmd/feedback.go @@ -0,0 +1,38 @@ +package cmd + +/* + * Copyright NetFoundry, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import ( + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/cli" + "github.com/spf13/cobra" +) + +// feedbackCmd represents the feedback command +var feedbackCmd = &cobra.Command{ + Use: "feedback", + Short: "fetch system information and logs from ziti-tunnel", + Long: `Fetch the system information and logs, zip it and attach it to the email. +User has to send the email to support@netfoundry.io`, + Run: func(cmd *cobra.Command, args []string) { + cli.GetFeedback(args, nil) + }, +} + +func init() { + rootCmd.AddCommand(feedbackCmd) +} diff --git a/service/ziti-tunnel/cmd/identities.go b/service/ziti-tunnel/cmd/identities.go index fcabe42da..41e4c2170 100644 --- a/service/ziti-tunnel/cmd/identities.go +++ b/service/ziti-tunnel/cmd/identities.go @@ -26,7 +26,7 @@ var servicesOfID bool // identitiesCmd represents the identities command var identitiesCmd = &cobra.Command{ - Use: "identities", + Use: "identities [all] [idname...] [-s]", Short: "Lists identities from ziti-tunnel", Long: `View the identities that this user has access to. The records will be fetched from ziti-tunnel`, @@ -45,7 +45,6 @@ func init() { // Cobra supports local flags which will only run when this command // is called directly, e.g.: - //identitiesCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") identitiesCmd.Flags().BoolVarP(&servicesOfID, "services", "s", false, "Display all services that belonged to the identity") } diff --git a/service/ziti-tunnel/cmd/identity.go b/service/ziti-tunnel/cmd/identity.go new file mode 100644 index 000000000..37294ab15 --- /dev/null +++ b/service/ziti-tunnel/cmd/identity.go @@ -0,0 +1,55 @@ +package cmd + +/* + * Copyright NetFoundry, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import ( + "errors" + "strings" + + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/cli" + + "github.com/spf13/cobra" +) + +// identityCmd represents the identity command +var identityCmd = &cobra.Command{ + Use: "identity [fingerprint] [on/off]", + Short: "enable or disable the identity", + Long: `Enable or disable identity based on the On Off values. + It accepts finger print of the identity followed by on/off value`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("requires 2 arguments, usage: identity [fingerprint] [on/off]") + } + if len(args[0]) == 40 && isValidArg(args[1]) { + return nil + } + return errors.New("incorrect arguments are passed, usage: identity [fingerprint] [on/off]") + }, + Run: func(cmd *cobra.Command, args []string) { + cli.OnOffIdentity(args, nil) + }, +} + +func isValidArg(onOff string) bool { + return (strings.EqualFold(onOff, "on") || strings.EqualFold(onOff, "off")) +} + +func init() { + rootCmd.AddCommand(identityCmd) +} diff --git a/service/ziti-tunnel/cmd/loglevel.go b/service/ziti-tunnel/cmd/loglevel.go new file mode 100644 index 000000000..b43d5f472 --- /dev/null +++ b/service/ziti-tunnel/cmd/loglevel.go @@ -0,0 +1,57 @@ +package cmd + +/* + * Copyright NetFoundry, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import ( + "errors" + + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/cli" + "github.com/spf13/cobra" +) + +var currentLogLevel bool + +// loglevelCmd represents the loglevel command +var loglevelCmd = &cobra.Command{ + Use: "loglevel [loglevel]", + Short: "Set the loglevel of the ziti tunnel", + Long: `Allows you to set the log level of ziti tunnel.`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 && !currentLogLevel { + return errors.New("requires 1 argument or a flag, examples of the accepted loglevel arguments are trace, info, debug etc") + } + return nil + + }, + Run: func(cmd *cobra.Command, args []string) { + flags := map[string]bool{} + flags["query"] = currentLogLevel + cli.SetLogLevel(args, flags) + }, +} + +func init() { + rootCmd.AddCommand(loglevelCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + loglevelCmd.PersistentFlags().BoolVarP(¤tLogLevel, "query", "q", false, "Query current loglevel") + +} diff --git a/service/ziti-tunnel/cmd/root.go b/service/ziti-tunnel/cmd/root.go index 0a3cc12a4..7fb81ba5a 100644 --- a/service/ziti-tunnel/cmd/root.go +++ b/service/ziti-tunnel/cmd/root.go @@ -19,6 +19,7 @@ package cmd import ( "fmt" + "log" "os" "github.com/spf13/cobra" @@ -75,5 +76,5 @@ func initConfig() { } func checkHelp() { - fmt.Println("Use -h or --help option to get the help message") + log.Println("Use -h or --help option to get the help message") } diff --git a/service/ziti-tunnel/cmd/services.go b/service/ziti-tunnel/cmd/services.go index b752294aa..3ae391597 100644 --- a/service/ziti-tunnel/cmd/services.go +++ b/service/ziti-tunnel/cmd/services.go @@ -24,7 +24,7 @@ import ( // servicesCmd represents the services command var servicesCmd = &cobra.Command{ - Use: "services", + Use: "services [all] [svc_name...]", Short: "Lists services from ziti-tunnel", Long: `View the services that this user has access to. The records will be fetched from ziti-tunnel`, @@ -37,5 +37,4 @@ var servicesCmd = &cobra.Command{ func init() { listCmd.AddCommand(servicesCmd) - } diff --git a/service/ziti-tunnel/constants/consts.go b/service/ziti-tunnel/constants/consts.go index 029611028..cc1c95eae 100644 --- a/service/ziti-tunnel/constants/consts.go +++ b/service/ziti-tunnel/constants/consts.go @@ -17,9 +17,9 @@ package constants -const( +const ( Ipv4ip = "100.64.0.1" - Ipv4MaxMask = 8 - Ipv4MinMask = 16 + Ipv4MaxMask = 8 + Ipv4MinMask = 16 Ipv4DefaultMask = 10 -) \ No newline at end of file +) diff --git a/service/ziti-tunnel/dto/clidtos.go b/service/ziti-tunnel/dto/clidtos.go index 1f6b544c4..6f7e840d2 100644 --- a/service/ziti-tunnel/dto/clidtos.go +++ b/service/ziti-tunnel/dto/clidtos.go @@ -1,37 +1,46 @@ -package dto - -/* - * Copyright NetFoundry, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -type IdentityCli struct { - Name string - FingerPrint string - Active bool - Config string - ControllerVersion string - Status string -} - -type ServiceCli struct { - Name string - AssignedIP string - InterceptHost string - InterceptPort uint16 - Id string - AssignedHost string - OwnsIntercept bool -} +package dto + +/* + * Copyright NetFoundry, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +type IdentityCli struct { + Name string + FingerPrint string + Active bool + Config string + ControllerVersion string + Status string +} + +type ServiceCli struct { + Name string + Id string + Protocols string + Ports string + Addresses string +} + +type IdentityOnOffPayload struct { + OnOff string + Fingerprint string +} + +type MonitorServiceResponse struct { + Code int + Message string + Error string +} diff --git a/service/ziti-tunnel/dto/dtos.go b/service/ziti-tunnel/dto/dtos.go index 5837fd45a..d63105f6f 100644 --- a/service/ziti-tunnel/dto/dtos.go +++ b/service/ziti-tunnel/dto/dtos.go @@ -18,10 +18,11 @@ package dto import ( + "log" + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/config" idcfg "github.com/openziti/sdk-golang/ziti/config" "github.com/openziti/sdk-golang/ziti/enroll" - "log" ) type AddIdentity struct { @@ -162,6 +163,11 @@ type IdentityEvent struct { Id Identity } +type LogLevelEvent struct { + ActionEvent + LogLevel string +} + type MfaEvent struct { ActionEvent Fingerprint string diff --git a/service/ziti-tunnel/dto/events.go b/service/ziti-tunnel/dto/events.go index fbe8413dd..c216ced3b 100644 --- a/service/ziti-tunnel/dto/events.go +++ b/service/ziti-tunnel/dto/events.go @@ -21,16 +21,20 @@ const ( REMOVED = "removed" ERROR = "error" UPDATED = "updated" + CHANGED = "changed" + NORMAL = "Normal" SERVICE_OP = "service" IDENTITY_OP = "identity" + LOGLEVEL_OP = "logLevel" + FEEDBACK_OP = "CaptureLogs" MFA_OP = "mfa" - MFAEnrollmentChallengAtion = "enrollment_challenge" - MFAEnrollmentVerificationAction = "enrollment_verification" - MFAEnrollmentRemovedAction = "enrollment_remove" + MFAEnrollmentChallengAtion = "enrollment_challenge" + MFAEnrollmentVerificationAction = "enrollment_verification" + MFAEnrollmentRemovedAction = "enrollment_remove" - MFA_AUTH_CHALLENGE_ACTION = "auth_challenge" + MFA_AUTH_CHALLENGE_ACTION = "auth_challenge" ) var SERVICE_ADDED = ActionEvent{ @@ -50,6 +54,16 @@ var IDENTITY_REMOVED = ActionEvent{ StatusEvent: StatusEvent{Op: IDENTITY_OP}, Action: REMOVED, } +var LOGLEVEL_CHANGED = ActionEvent{ + StatusEvent: StatusEvent{Op: LOGLEVEL_OP}, + Action: CHANGED, +} + +var FEEDBACK_REQUEST = ActionEvent{ + StatusEvent: StatusEvent{Op: FEEDBACK_OP}, + Action: NORMAL, +} + var IdentityUpdateComplete = ActionEvent{ StatusEvent: StatusEvent{Op: IDENTITY_OP}, Action: UPDATED, diff --git a/service/ziti-tunnel/main.go b/service/ziti-tunnel/main.go index 6486834e3..c1c3f236f 100644 --- a/service/ziti-tunnel/main.go +++ b/service/ziti-tunnel/main.go @@ -105,6 +105,12 @@ func main() { fmt.Println(version()) case "list": commandline.Execute() + case "identity": + commandline.Execute() + case "loglevel": + commandline.Execute() + case "feedback": + commandline.Execute() default: usage(fmt.Sprintf("invalid command %s", cmd)) } @@ -120,7 +126,7 @@ func usage(errmsg string) { "%s\n\n"+ "usage: %s \n"+ " where is one of\n"+ - " install, remove, debug, start, stop, pause, continue, list or version.\n", + " install, remove, debug, start, stop, pause, continue, list, identity, loglevel, feedback or version.\n", errmsg, os.Args[0]) os.Exit(2) } diff --git a/service/ziti-tunnel/service/debug.go b/service/ziti-tunnel/service/debug.go index 6da5bdd4e..47f87435c 100644 --- a/service/ziti-tunnel/service/debug.go +++ b/service/ziti-tunnel/service/debug.go @@ -18,8 +18,8 @@ package service import ( - idcfg "github.com/openziti/sdk-golang/ziti/config" "github.com/openziti/desktop-edge-win/service/ziti-tunnel/dto" + idcfg "github.com/openziti/sdk-golang/ziti/config" ) func dbg() { @@ -28,15 +28,15 @@ func dbg() { events.broadcast <- dto.TunnelStatusEvent{ StatusEvent: dto.StatusEvent{Op: "status"}, Status: r, - ApiVersion: API_VERSION, + ApiVersion: API_VERSION, } svcs := make([]*dto.Service, 2) svcs[0] = &dto.Service{ - Name: "FakeService1", + Name: "FakeService1", } svcs[1] = &dto.Service{ - Name: "Second Fake Service", + Name: "Second Fake Service", } events.broadcast <- dto.IdentityEvent{ @@ -57,7 +57,7 @@ func dbg() { events.broadcast <- dto.ServiceEvent{ ActionEvent: dto.SERVICE_ADDED, Service: &dto.Service{ - Name: "New Service", + Name: "New Service", }, Fingerprint: "new_id_fingerprint", } diff --git a/service/ziti-tunnel/service/install.go b/service/ziti-tunnel/service/install.go index 65910e420..7ec0c3dbd 100644 --- a/service/ziti-tunnel/service/install.go +++ b/service/ziti-tunnel/service/install.go @@ -52,9 +52,9 @@ func InstallService() error { log.Infof("service installed using path: %s", fullPath) s, err = m.CreateService(SvcStartName, fullPath, mgr.Config{ - StartType: mgr.StartAutomatic, - DisplayName: SvcName, - Description: SvcNameLong, + StartType: mgr.StartAutomatic, + DisplayName: SvcName, + Description: SvcNameLong, }) if err != nil { return err @@ -89,4 +89,4 @@ func RemoveService() error { return fmt.Errorf("RemoveEventLogSource() failed: %s", err) } return nil -} \ No newline at end of file +} diff --git a/service/ziti-tunnel/service/ipc.go b/service/ziti-tunnel/service/ipc.go index c54f4cdde..04aca3fd5 100644 --- a/service/ziti-tunnel/service/ipc.go +++ b/service/ziti-tunnel/service/ipc.go @@ -500,6 +500,10 @@ func serveIpc(conn net.Conn) { //save the state rts.SaveState() + case "NotifyLogLevelUIAndUpdateService": + sendLogLevelAndNotify(enc, cmd.Payload["Level"].(string)) + case "NotifyIdentityUI": + sendIdentityAndNotifyUI(enc, cmd.Payload["Fingerprint"].(string)) case "ZitiDump": log.Debug("request to ZitiDump received") for _, id := range rts.ids { @@ -732,7 +736,7 @@ func toggleIdentity(out *json.Encoder, fingerprint string, onOff bool) { msg := fmt.Sprintf("identity with fingerprint %s not found", fingerprint) log.Warn(msg) respond(out, dto.Response{ - Code: SUCCESS, + Code: IDENTITY_NOT_FOUND, Message: fmt.Sprintf("no update performed. %s", msg), Error: "", Payload: nil, @@ -1033,7 +1037,15 @@ func handleEvents(isInitialized chan struct{}) { //Removes the Config from the provided identity and returns a 'cleaned' id func Clean(src *Id) dto.Identity { - log.Tracef("cleaning identity: %s %v %v", src.Name, src.CId.MfaNeeded, src.CId.MfaEnabled) + mfaNeeded := false + mfaEnabled := false + + if src.CId != nil { + mfaNeeded = src.CId.MfaNeeded + mfaEnabled = src.CId.MfaEnabled + } + + log.Tracef("cleaning identity: %s", src.Name, mfaNeeded, mfaEnabled) AddMetrics(src) nid := dto.Identity{ Name: src.Name, @@ -1042,8 +1054,8 @@ func Clean(src *Id) dto.Identity { Config: idcfg.Config{}, ControllerVersion: src.ControllerVersion, Status: "", - MfaNeeded: src.CId.MfaNeeded, - MfaEnabled: src.CId.MfaEnabled, + MfaNeeded: mfaNeeded, + MfaEnabled: mfaEnabled, Services: make([]*dto.Service, 0), Metrics: src.Metrics, Tags: nil, @@ -1083,3 +1095,34 @@ func authMfa(out *json.Encoder, fingerprint string, code string) { respondWithError(out, fmt.Sprintf("AuthMFA failed. the supplied code [%s] was not valid: %s", code, result), 1, result) } } + +// when the log level is updated through command line, the message is broadcasted to UI and update service as well +func sendLogLevelAndNotify(enc *json.Encoder, loglevel string) { + events.broadcast <- dto.LogLevelEvent{ + ActionEvent: dto.LOGLEVEL_CHANGED, + LogLevel: loglevel, + } + message := fmt.Sprintf("Loglevel %s is sent to the events channel", loglevel) + log.Info(message) + resp := dto.Response{Message: "success", Code: SUCCESS, Error: "", Payload: message} + respond(enc, resp) +} + +// when the identity status is updated through command line, the message is sent to UI as well +func sendIdentityAndNotifyUI(enc *json.Encoder, fingerprint string) { + for _, id := range rts.ids { + if id.FingerPrint == fingerprint { + events.broadcast <- dto.IdentityEvent{ + ActionEvent: dto.IDENTITY_ADDED, + Id: id.Identity, + } + message := fmt.Sprintf("Identity %s - %s updated message is sent to the events channel", id.Identity.Name, id.Identity.FingerPrint) + log.Info(message) + resp := dto.Response{Message: "success", Code: SUCCESS, Error: "", Payload: message} + respond(enc, resp) + return + } + } + resp := dto.Response{Message: "", Code: ERROR, Error: "Could not find id matching fingerprint " + fingerprint, Payload: ""} + respond(enc, resp) +} diff --git a/service/ziti-tunnel/service/orphaned-identities.go b/service/ziti-tunnel/service/orphaned-identities.go index af6f5c10e..470d1863f 100644 --- a/service/ziti-tunnel/service/orphaned-identities.go +++ b/service/ziti-tunnel/service/orphaned-identities.go @@ -1,101 +1,101 @@ -package service - -import ( - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/openziti/desktop-edge-win/service/ziti-tunnel/config" -) - -// After System update, the identity files are not getting copied to the config path -// So we are adding a function to scan for identities in the backtup location -func scanForIdentitiesPostWindowsUpdate() error { - srcBackUpPaths := [2]string{"Windows.~BT\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\NetFoundry", - "Windows.old\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\NetFoundry"} - systemDrivePath := os.Getenv("SystemDrive") - if systemDrivePath == "" { - return nil - } - for _, srcPath := range srcBackUpPaths { - sourcePath := filepath.Join(systemDrivePath, string(os.PathSeparator), srcPath) - _, err := os.Stat(sourcePath) - if err != nil { - log.Debugf("Folder %s does not exist", sourcePath) - continue - } - err = searchAndCopyFilesFromBackup(sourcePath) - if err != nil { - log.Debugf("Copy files from %s failed, %v", sourcePath, err) - } - } - return nil -} - -func searchAndCopyFilesFromBackup(srcPath string) error { - err := filepath.Walk(srcPath, copyFilesFromBackUp) - if err != nil { - return err - } - return nil -} - -func copyFilesFromBackUp(path string, f os.FileInfo, err error) error { - if !f.IsDir() { - log.Infof("Found: %s", path) - destinationFile := filepath.Join(config.Path(), string(os.PathSeparator), f.Name()) - //check if the file is present in the destination folder - _, err := os.Stat(destinationFile) - if err == nil && !strings.Contains(f.Name(), ConfigFileName) { - log.Debugf("File %s is already present in the config path and it is not %s, So not transfering", f.Name(), ConfigFileName) - deleteFile(path) - return nil - } - _, err = copy(path, destinationFile) - if err != nil { - log.Errorf("Error occurred while copying the Windows backup folder %s --> %s %v", f.Name(), destinationFile, err) - return nil - } else { - log.Infof("Found backup file at %s. Restored this file to %s", path, destinationFile) - deleteFile(path) - } - } - return nil -} -func deleteFile(path string) { - log.Infof("Removing file %s", path) - err := os.Remove(path) - if err != nil { - log.Errorf("Error occured while removing the file %s, %v", path, err) - } else { - log.Infof("Removed file %s", path) - } -} - -func copy(src, dst string) (int64, error) { - sourceFileStat, err := os.Stat(src) - if err != nil { - return 0, err - } - - if !sourceFileStat.Mode().IsRegular() { - return 0, errors.New(fmt.Sprintf("%s is not a regular file", src)) - } - - source, err := os.Open(src) - if err != nil { - return 0, err - } - defer source.Close() - - destination, err := os.Create(dst) - if err != nil { - return 0, err - } - defer destination.Close() - nBytes, err := io.Copy(destination, source) - return nBytes, err -} +package service + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/openziti/desktop-edge-win/service/ziti-tunnel/config" +) + +// After System update, the identity files are not getting copied to the config path +// So we are adding a function to scan for identities in the backtup location +func scanForIdentitiesPostWindowsUpdate() error { + srcBackUpPaths := [2]string{"Windows.~BT\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\NetFoundry", + "Windows.old\\Windows\\System32\\config\\systemprofile\\AppData\\Roaming\\NetFoundry"} + systemDrivePath := os.Getenv("SystemDrive") + if systemDrivePath == "" { + return nil + } + for _, srcPath := range srcBackUpPaths { + sourcePath := filepath.Join(systemDrivePath, string(os.PathSeparator), srcPath) + _, err := os.Stat(sourcePath) + if err != nil { + log.Debugf("Folder %s does not exist", sourcePath) + continue + } + err = searchAndCopyFilesFromBackup(sourcePath) + if err != nil { + log.Debugf("Copy files from %s failed, %v", sourcePath, err) + } + } + return nil +} + +func searchAndCopyFilesFromBackup(srcPath string) error { + err := filepath.Walk(srcPath, copyFilesFromBackUp) + if err != nil { + return err + } + return nil +} + +func copyFilesFromBackUp(path string, f os.FileInfo, err error) error { + if !f.IsDir() { + log.Infof("Found: %s", path) + destinationFile := filepath.Join(config.Path(), string(os.PathSeparator), f.Name()) + //check if the file is present in the destination folder + _, err := os.Stat(destinationFile) + if err == nil && !strings.Contains(f.Name(), ConfigFileName) { + log.Debugf("File %s is already present in the config path and it is not %s, So not transfering", f.Name(), ConfigFileName) + deleteFile(path) + return nil + } + _, err = copy(path, destinationFile) + if err != nil { + log.Errorf("Error occurred while copying the Windows backup folder %s --> %s %v", f.Name(), destinationFile, err) + return nil + } else { + log.Infof("Found backup file at %s. Restored this file to %s", path, destinationFile) + deleteFile(path) + } + } + return nil +} +func deleteFile(path string) { + log.Infof("Removing file %s", path) + err := os.Remove(path) + if err != nil { + log.Errorf("Error occured while removing the file %s, %v", path, err) + } else { + log.Infof("Removed file %s", path) + } +} + +func copy(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, errors.New(fmt.Sprintf("%s is not a regular file", src)) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + nBytes, err := io.Copy(destination, source) + return nBytes, err +} diff --git a/service/ziti-tunnel/service/permissions.go b/service/ziti-tunnel/service/permissions.go index 8525a1d66..39fe88463 100644 --- a/service/ziti-tunnel/service/permissions.go +++ b/service/ziti-tunnel/service/permissions.go @@ -53,4 +53,4 @@ func EnsurePermissions(group string) string { } return sid -} \ No newline at end of file +} diff --git a/service/ziti-tunnel/service/pkg-vars.go b/service/ziti-tunnel/service/pkg-vars.go index 264a08c3d..e081df8b9 100644 --- a/service/ziti-tunnel/service/pkg-vars.go +++ b/service/ziti-tunnel/service/pkg-vars.go @@ -59,8 +59,8 @@ const ( IDENTITY_NOT_FOUND = 1000 MFA_FAILED_TO_GENERATE_CODES = 200 - MFA_FAILED_TO_RETURN_CODES = 201 - MFA_FINGERPRINT_NOT_FOUND = 202 + MFA_FAILED_TO_RETURN_CODES = 201 + MFA_FINGERPRINT_NOT_FOUND = 202 DEFAULT_REFRESH_INTERVAL = 10 diff --git a/service/ziti-tunnel/service/service.go b/service/ziti-tunnel/service/service.go index fbf72ca43..3ddd42d0f 100644 --- a/service/ziti-tunnel/service/service.go +++ b/service/ziti-tunnel/service/service.go @@ -87,7 +87,7 @@ loop: changes <- svc.Status{State: svc.StopPending} log.Infof("waiting for shutdown to complete") - <- control + <-control log.Infof("normal shutdown complete") return } diff --git a/service/ziti-tunnel/service/state.go b/service/ziti-tunnel/service/state.go index 0c5dfd6e5..26933a70b 100644 --- a/service/ziti-tunnel/service/state.go +++ b/service/ziti-tunnel/service/state.go @@ -152,9 +152,9 @@ func (t *RuntimeState) ToMetrics() dto.TunnelStatus { Name: id.Name, FingerPrint: id.FingerPrint, Metrics: id.Metrics, - Active: id.Active, - MfaEnabled: id.MfaEnabled, - MfaNeeded: id.MfaNeeded, + Active: id.Active, + MfaEnabled: id.MfaEnabled, + MfaNeeded: id.MfaNeeded, } i++ } diff --git a/service/ziti-tunnel/service/topic.go b/service/ziti-tunnel/service/topic.go index de09edab7..c6d95897d 100644 --- a/service/ziti-tunnel/service/topic.go +++ b/service/ziti-tunnel/service/topic.go @@ -19,8 +19,8 @@ package service type topic struct { broadcast chan interface{} - channels map[int]chan interface{} - done chan bool + channels map[int]chan interface{} + done chan bool } func newTopic(cap int16) topic { @@ -31,19 +31,19 @@ func newTopic(cap int16) topic { } } -func(t *topic) register(id int, c chan interface{}) { +func (t *topic) register(id int, c chan interface{}) { t.channels[id] = c } -func(t *topic) unregister(id int) { +func (t *topic) unregister(id int) { delete(t.channels, id) } -func(t *topic) shutdown() { +func (t *topic) shutdown() { t.done <- true } -func(t *topic) run() { +func (t *topic) run() { go func() { for { select { diff --git a/service/ziti-tunnel/service/types.go b/service/ziti-tunnel/service/types.go index 973652738..67ec14950 100644 --- a/service/ziti-tunnel/service/types.go +++ b/service/ziti-tunnel/service/types.go @@ -8,4 +8,4 @@ import ( type Id struct { dto.Identity CId *cziti.ZIdentity -} \ No newline at end of file +} diff --git a/service/ziti-tunnel/util/detect-ip-changes.go b/service/ziti-tunnel/util/detect-ip-changes.go index 9044090e0..077d21e50 100644 --- a/service/ziti-tunnel/util/detect-ip-changes.go +++ b/service/ziti-tunnel/util/detect-ip-changes.go @@ -29,10 +29,10 @@ var ( modws2_32 = windows.NewLazySystemDLL("ws2_32.dll") modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") - procWSACreateEvent = modws2_32.NewProc("WSACreateEvent") - procNotifyAddrChange = modiphlpapi.NewProc("NotifyAddrChange") + procWSACreateEvent = modws2_32.NewProc("WSACreateEvent") + procNotifyAddrChange = modiphlpapi.NewProc("NotifyAddrChange") procNotifyRouteChange = modiphlpapi.NewProc("NotifyRouteChange") - log = logging.Logger() + log = logging.Logger() ) func OnIPChange(callback func()) { @@ -43,7 +43,7 @@ func OnIPChange(callback func()) { log.Debugf("Symbol [NotifyAddrChange] loaded at %#v", procNotifyAddrChange.Addr()) var ( - err error + err error overlap *windows.Overlapped = &windows.Overlapped{} ) @@ -84,4 +84,4 @@ func WSACreateEvent() (windows.Handle, error) { } else { return windows.Handle(handlePtr), nil } -} \ No newline at end of file +} diff --git a/service/ziti-tunnel/util/iputil/iputil.go b/service/ziti-tunnel/util/iputil/iputil.go index 838664de6..b9ad861c9 100644 --- a/service/ziti-tunnel/util/iputil/iputil.go +++ b/service/ziti-tunnel/util/iputil/iputil.go @@ -35,7 +35,7 @@ func Ipv4Inc(ip net.IP, maskBits int) net.IP { newIpAsInt := ipAsInt + 1 - return Uint32ToIpv4(baseIp + newIpAsInt & uint32(ipMask)) + return Uint32ToIpv4(baseIp + newIpAsInt&uint32(ipMask)) } func Ipv4ToUint32(ip net.IP) uint32 { if len(ip) == 16 { diff --git a/service/ziti-tunnel/util/logging/loghelper.go b/service/ziti-tunnel/util/logging/loghelper.go index d2981f258..8005ead84 100644 --- a/service/ziti-tunnel/util/logging/loghelper.go +++ b/service/ziti-tunnel/util/logging/loghelper.go @@ -48,16 +48,13 @@ func init() { setConsoleModeProc := kernel32DLL.NewProc("SetConsoleMode") setConsoleModeProc.Call(uintptr(handle), 0x0001|0x0002|0x0004) - - with := &dateFormatterNoFilename{ - } + with := &dateFormatterNoFilename{} /*with := &dateFormatterWithFilename{ }*/ with.dateFormatter.timeFormat = UTCFormat() withFilenameLogger.SetFormatter(with) - without := &dateFormatterNoFilename{ - } + without := &dateFormatterNoFilename{} without.dateFormatter.timeFormat = UTCFormat() noFilenamelogger.SetFormatter(without) } @@ -70,7 +67,7 @@ func NoFilenameLogger() *logrus.Logger { return noFilenamelogger } -func SetLoggingLevel(goLevel logrus.Level){ +func SetLoggingLevel(goLevel logrus.Level) { withFilenameLogger.SetLevel(goLevel) noFilenamelogger.SetLevel(goLevel) } @@ -84,8 +81,8 @@ func initLogger(logger *logrus.Logger, level logrus.Level) { logger.SetReportCaller(true) - rl, _ := rotatelogs.New(config.LogFile() + ".%Y%m%d%H%M.log", - rotatelogs.WithRotationTime(24 * time.Hour), + rl, _ := rotatelogs.New(config.LogFile()+".%Y%m%d%H%M.log", + rotatelogs.WithRotationTime(24*time.Hour), rotatelogs.WithRotationCount(7), rotatelogs.WithLinkName(config.LogFile())) @@ -205,4 +202,4 @@ var errorColor = ansi.Red + "ERROR" + ansi.DefaultFG var warnColor = ansi.Yellow + " WARN" + ansi.DefaultFG var infoColor = ansi.White + " INFO" + ansi.DefaultFG var debugColor = ansi.Blue + "DEBUG" + ansi.DefaultFG -var traceColor = ansi.LightBlack + "TRACE" + ansi.DefaultFG \ No newline at end of file +var traceColor = ansi.LightBlack + "TRACE" + ansi.DefaultFG diff --git a/service/ziti-tunnel/version.go b/service/ziti-tunnel/version.go index df69fcaac..f8a72b85b 100644 --- a/service/ziti-tunnel/version.go +++ b/service/ziti-tunnel/version.go @@ -20,9 +20,9 @@ package main const ( - Version = "1.9.1" - Revision = "40dcdda233d7" - Branch = "main" + Version = "1.9.2" + Revision = "e1789c51e4f4" + Branch = "release-next" BuildUser = "" - BuildDate = "2021-04-06 17:45:45" + BuildDate = "2021-04-06 20:24:07" ) diff --git a/version b/version index 9ab8337f3..8fdcf3869 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.9.1 +1.9.2