diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..6c34532c
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,17 @@
+# Code of Conduct
+
+All open source projects managed by NetFoundry share a common [code of
+conduct](https://netfoundry.github.io/policies/CODE_OF_CONDUCT.html) which all contributors are expected to follow.
+Please be sure you read, understand and adhere to the thoughts expressed therein.
+
+Individuals acting in any way that is considered in violation of the Code of Conduct will receive corrective actions
+appropriate for the violation. It is possible to act in ways which technically do not violate the Code of Conduct but
+are clearly against the spirit of the project. These situations will also be considered on a case by case basis and if
+necessary the Code of Conduct will be revised accordingly and approrpiate notifications will be made to the community
+and to the violator.
+
+## Violations
+
+Contributors violating the Code of Conduct will receive public notification of the violation. Continued violations or
+aggregious violations of the Code of Conduct will result in the contributor being removed from the contributors list as
+well as further contributions being declined.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..03bb09f0
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,6 @@
+# Contributing
+
+NetFoundry welcomes all and any contributions. All open source projects managed by NetFoundry share a common
+[guide for contributions](https://netfoundry.github.io/policies/CONTRIBUTING.html).
+
+If you are eager to contribute to a NetFoundry-managed open source project please read and act accordingly.
diff --git a/DesktopEdge/MainWindow.xaml.cs b/DesktopEdge/MainWindow.xaml.cs
index 191ea800..e8eecdea 100644
--- a/DesktopEdge/MainWindow.xaml.cs
+++ b/DesktopEdge/MainWindow.xaml.cs
@@ -131,6 +131,8 @@ namespace ZitiDesktopEdge {
}
} else if (mfa.Action == "enrollment_remove") {
// ShowBlurb("removed mfa: " + mfa.Successful, "");
+ } else if (mfa.Action == "mfa_auth_status") {
+ // ShowBlurb("mfa authenticated: " + mfa.Successful, "");
} else {
await ShowBlurbAsync ("Unexpected error when processing MFA", "");
logger.Error("unexpected action: " + mfa.Action);
@@ -509,6 +511,7 @@ namespace ZitiDesktopEdge {
serviceClient.OnMfaEvent += ServiceClient_OnMfaEvent;
serviceClient.OnLogLevelEvent += ServiceClient_OnLogLevelEvent;
serviceClient.OnBulkServiceEvent += ServiceClient_OnBulkServiceEvent;
+ serviceClient.OnNotificationEvent += ServiceClient_OnNotificationEvent;
Application.Current.Properties.Add("ServiceClient", serviceClient);
monitorClient = new MonitorClient();
@@ -570,7 +573,39 @@ namespace ZitiDesktopEdge {
}
}
- string nextVersionStr = null;
+ private void ServiceClient_OnNotificationEvent(object sender, NotificationEvent e) {
+ var displayMFARequired = false;
+ foreach (var notification in e.Notification) {
+ var found = identities.Find(id => id.Fingerprint == notification.Fingerprint);
+ if (found == null) {
+ logger.Warn($"{e.Op} event for {notification.Fingerprint} but the provided identity fingerprint was not found!");
+ continue;
+ }
+ if (notification.MfaMaximumTimeout == 0) {
+ found.MFAInfo.IsAuthenticated = false;
+ // display notification message - critical
+ displayMFARequired = true;
+ } else if (notification.MfaMinimumTimeout == 0) {
+ // display notification message, only few services are timed out
+ } else {
+ // display notification message, only few services are about to timeout
+ }
+ }
+ // we may need to display mfa icon, based on the timer in UI, remove found.MFAInfo.IsAuthenticated setting in this function.
+ // the below function can show mfa icon even after user authenticates successfully, in race conditions
+ if (displayMFARequired) {
+ LoadIdentities(true);
+ this.Dispatcher.Invoke(() => {
+ IdentityDetails deets = ((MainWindow)Application.Current.MainWindow).IdentityMenu;
+ if (deets.IsVisible) {
+ deets.UpdateView();
+ }
+ });
+ }
+ }
+
+
+ string nextVersionStr = null;
private void MonitorClient_OnReconnectFailure(object sender, object e) {
if (nextVersionStr == null) {
// check for the current version
@@ -653,7 +688,7 @@ namespace ZitiDesktopEdge {
IsUpdateAvailable = true;
MainMenu.ShowUpdateAvailable(evt.TimeRemaining, evt.InstallTime);
AlertCanvas.Visibility = Visibility.Visible;
- ShowToast("An Update is Available for Ziti Desktop Edge");
+ ShowToast("An Update is Available for Ziti Desktop Edge, will initiate auto installation by " + evt.InstallTime);
SetNotifyIcon("");
// display a tag in UI and a button for the update software
}
@@ -800,12 +835,16 @@ namespace ZitiDesktopEdge {
found.IsEnabled = zid.IsEnabled;
found.MFAInfo.IsAuthenticated = !e.Id.MfaNeeded;
LoadIdentities(true);
- return;
}
} else if (e.Action == "updated") {
//this indicates that all updates have been sent to the UI... wait for 2 seconds then trigger any ui updates needed
await Task.Delay(2000);
-
+ } else if (e.Action == "connected") {
+ //this indicates that all updates have been sent to the UI... wait for 2 seconds then trigger any ui updates needed
+ await Task.Delay(2000);
+ } else if (e.Action == "disconnected") {
+ //this indicates that all updates have been sent to the UI... wait for 2 seconds then trigger any ui updates needed
+ await Task.Delay(2000);
} else {
IdentityForgotten(ZitiIdentity.FromClient(e.Id));
}
diff --git a/DesktopEdge/Properties/AssemblyInfo.cs b/DesktopEdge/Properties/AssemblyInfo.cs
index 4e3bd4b5..4b76b04f 100644
--- a/DesktopEdge/Properties/AssemblyInfo.cs
+++ b/DesktopEdge/Properties/AssemblyInfo.cs
@@ -50,6 +50,6 @@ using System.Windows;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.9.9.0")]
-[assembly: AssemblyVersion("1.9.9.0")]
-[assembly: AssemblyFileVersion("1.9.9.0")]
+// [assembly: AssemblyVersion("1.10.0.0")]
+[assembly: AssemblyVersion("1.10.0.0")]
+[assembly: AssemblyFileVersion("1.10.0.0")]
diff --git a/DesktopEdge/Views/Screens/MainMenu.xaml b/DesktopEdge/Views/Screens/MainMenu.xaml
index bd88983c..8ab38a37 100644
--- a/DesktopEdge/Views/Screens/MainMenu.xaml
+++ b/DesktopEdge/Views/Screens/MainMenu.xaml
@@ -188,6 +188,7 @@
+
diff --git a/DesktopEdge/Views/Screens/MainMenu.xaml.cs b/DesktopEdge/Views/Screens/MainMenu.xaml.cs
index 87f5d9b5..d1b2bb56 100644
--- a/DesktopEdge/Views/Screens/MainMenu.xaml.cs
+++ b/DesktopEdge/Views/Screens/MainMenu.xaml.cs
@@ -509,6 +509,33 @@ namespace ZitiDesktopEdge {
}
}
+ ///
+ /// Save the frequency information to the properties and queue for update.
+ ///
+ async private void UpdateFrequency() {
+ logger.Info("updating frequency...");
+ DataClient client = (DataClient)Application.Current.Properties["ServiceClient"];
+ try {
+ var newFrequencyVar = 5; // Int32.Parse(FrequencyNew.Text);
+
+ var r = await client.NotificationFrequencyPayloadAsync(newFrequencyVar);
+ if (r.Code != 0) {
+ this.OnShowBlurb?.Invoke("Error: " + r.Error);
+ logger.Debug("ERROR: {0} : {1}", r.Message, r.Error);
+ } else {
+ this.OnShowBlurb?.Invoke("Frequency Saved");
+ this.CloseEdit();
+ }
+ logger.Info("Got response from update frequency task : {0}", r);
+ } catch (DataStructures.ServiceException se) {
+ this.OnShowBlurb?.Invoke("Error: " + se.Message);
+ logger.Error(se, "service exception in update frequency check: {0}", se.Message);
+ } catch (Exception ex) {
+ this.OnShowBlurb?.Invoke("Error: " + ex.Message);
+ logger.Error(ex, "unexpected error in update frequency check: {0}", ex.Message);
+ }
+ }
+
///
/// Show the Edit Modal and blur the background
///
@@ -591,5 +618,9 @@ namespace ZitiDesktopEdge {
private void SaveConfig() {
this.UpdateConfig();
}
+
+ private void SaveFrequency() {
+ this.UpdateFrequency();
+ }
}
}
\ No newline at end of file
diff --git a/Installer/ZitiDesktopEdge.aip b/Installer/ZitiDesktopEdge.aip
index 138e20f8..1c3c8f44 100644
--- a/Installer/ZitiDesktopEdge.aip
+++ b/Installer/ZitiDesktopEdge.aip
@@ -1,5 +1,5 @@
-
+
@@ -23,10 +23,10 @@
-
+
-
+
@@ -45,6 +45,7 @@
+
@@ -52,6 +53,7 @@
+
@@ -94,6 +96,7 @@
+
@@ -260,6 +263,7 @@
+
@@ -336,6 +340,7 @@
+
diff --git a/Installer/build.ps1 b/Installer/build.ps1
index aeff0416..2083a5dc 100644
--- a/Installer/build.ps1
+++ b/Installer/build.ps1
@@ -24,7 +24,7 @@ msbuild ZitiDesktopEdge.sln /property:Configuration=Release
Pop-Location
-$ADV_INST_HOME = "C:\Program Files (x86)\Caphyon\Advanced Installer 18.3"
+$ADV_INST_HOME = "C:\Program Files (x86)\Caphyon\Advanced Installer 18.4"
$ADVINST = "${ADV_INST_HOME}\bin\x86\AdvancedInstaller.com"
$ADVPROJECT = "${scriptPath}\ZitiDesktopEdge.aip"
$installerVersion=(Get-Content -Path ${scriptPath}\..\version)
diff --git a/Installer/reg.bat b/Installer/reg.bat
index c8d030ac..044ca561 100644
--- a/Installer/reg.bat
+++ b/Installer/reg.bat
@@ -1 +1 @@
-"C:\Program Files (x86)\Caphyon\Advanced Installer 18.3\bin\x86\AdvancedInstaller.com" /register %1
+"C:\Program Files (x86)\Caphyon\Advanced Installer 18.4\bin\x86\AdvancedInstaller.com" /register %1
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..93e58374
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2019 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
+
+ http://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.
diff --git a/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs
index ccb5c643..78b90f4d 100644
--- a/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs
+++ b/ZitiDesktopEdge.Client/DataStructures/DataStructures.cs
@@ -321,6 +321,17 @@ namespace ZitiDesktopEdge.DataStructures {
}
}
+ public class Notification {
+ public string IdentityName { get; set; }
+ public string Fingerprint { get; set; }
+ public string Message { get; set; }
+ public int MfaMinimumTimeout { get; set; }
+ public int MfaMaximumTimeout { get; set; }
+ public int TimeDuration { get; set; }
+ public string Severity { get; set; }
+
+ }
+
public class ZitiTunnelStatus : SvcResponse
{
public TunnelStatus Status { get; set; }
@@ -415,6 +426,11 @@ namespace ZitiDesktopEdge.DataStructures {
public List Identities { get; set; }
}
+ public class NotificationEvent : StatusEvent {
+ public List Notification { get; set; }
+
+ }
+
public class ServiceEvent : ActionEvent {
public string Fingerprint { get; set; }
public Service Service { get; set; }
@@ -494,4 +510,19 @@ namespace ZitiDesktopEdge.DataStructures {
public ConfigPayload Payload { get; set; }
}
+ public class NotificationFrequencyPayload {
+ public int NotificationFrequency { get; set; }
+ }
+
+ public class NotificationFrequencyFunction: ServiceFunction {
+ public NotificationFrequencyFunction(int notificationFrequency) {
+ this.Function = "UpdateFrequency";
+ this.Payload = new NotificationFrequencyPayload() {
+ NotificationFrequency = notificationFrequency
+ };
+ }
+
+ public NotificationFrequencyPayload Payload { get; set; }
+ }
+
}
diff --git a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs
index 62670c69..d5bbac14 100644
--- a/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs
+++ b/ZitiDesktopEdge.Client/ServiceClient/DataClient.cs
@@ -36,6 +36,7 @@ namespace ZitiDesktopEdge.ServiceClient {
public event EventHandler OnLogLevelEvent;
public event EventHandler OnMfaEvent;
public event EventHandler OnBulkServiceEvent;
+ public event EventHandler OnNotificationEvent;
protected override void ShutdownEvent(StatusEvent e) {
Logger.Debug("Clean shutdown detected from ziti");
@@ -71,6 +72,10 @@ namespace ZitiDesktopEdge.ServiceClient {
OnMfaEvent?.Invoke(this, e);
}
+ protected virtual void NotificationEvent(NotificationEvent e) {
+ OnNotificationEvent?.Invoke(this, e);
+ }
+
protected override void ClientConnected(object e) {
base.ClientConnected(e);
}
@@ -176,6 +181,7 @@ namespace ZitiDesktopEdge.ServiceClient {
Function = "RemoveIdentity",
Payload = new FingerprintPayload() { Fingerprint = fingerPrint }
};
+ Logger.Info("Removing Identity with fingerprint {0}", fingerPrint);
await sendAsync(removeFunction);
var r = await readAsync(ipcReader);
} catch (Exception ioe) {
@@ -367,6 +373,11 @@ namespace ZitiDesktopEdge.ServiceClient {
MfaEvent mfa = serializer.Deserialize(jsonReader);
MfaEvent(mfa);
break;
+ case "notification":
+ Logger.Debug("Notification event received");
+ NotificationEvent notificationEvent = serializer.Deserialize(jsonReader);
+ NotificationEvent(notificationEvent);
+ break;
default:
Logger.Debug("unexpected operation! " + evt.Op);
break;
@@ -414,6 +425,30 @@ namespace ZitiDesktopEdge.ServiceClient {
return resp;
}
+ async public Task NotificationFrequencyPayloadAsync(int frequency) {
+ SvcResponse resp = null;
+ try {
+
+ NotificationFrequencyFunction frequencyPayload = new NotificationFrequencyFunction(frequency);
+
+ await sendAsync(frequencyPayload);
+ resp = await readAsync(ipcReader);
+ Logger.Debug("frequency update payload is sent to the ziti tunnel");
+ } catch (Exception ex) {
+ //almost certainly a problem with the pipe - recreate the pipe...
+ //setupPipe();
+ //throw;
+ Logger.Error(ex, "Unexpected error");
+ CommunicationError(ex);
+ throw ex;
+ }
+ if (resp?.Code != 0) {
+ Logger.Warn("failed to update the frequency. {0} {1}", resp.Message, resp.Error);
+ throw new ServiceException("Failed to update the frequency", resp.Code, "Un expected error.");
+ }
+ return resp;
+ }
+
async public Task debugAsync() {
try {
await sendAsync(new ServiceFunction() { Function = "Debug" });
diff --git a/ZitiUpdateService/App.config b/ZitiUpdateService/App.config
index d933c958..355a075b 100644
--- a/ZitiUpdateService/App.config
+++ b/ZitiUpdateService/App.config
@@ -11,7 +11,7 @@
This operation is should probably remain high such as "1:0:0" (every hour) or "1:0:0:0" (every day)
-->
-
+
diff --git a/ZitiUpdateService/Properties/AssemblyInfo.cs b/ZitiUpdateService/Properties/AssemblyInfo.cs
index d56ff0f6..fbe51c90 100644
--- a/ZitiUpdateService/Properties/AssemblyInfo.cs
+++ b/ZitiUpdateService/Properties/AssemblyInfo.cs
@@ -30,6 +30,6 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.9.9.0")]
-[assembly: AssemblyVersion("1.9.9.0")]
-[assembly: AssemblyFileVersion("1.9.9.0")]
+// [assembly: AssemblyVersion("1.10.0.0")]
+[assembly: AssemblyVersion("1.10.0.0")]
+[assembly: AssemblyFileVersion("1.10.0.0")]
diff --git a/ZitiUpdateService/UpdateService.cs b/ZitiUpdateService/UpdateService.cs
index 90a91436..a9a18e59 100644
--- a/ZitiUpdateService/UpdateService.cs
+++ b/ZitiUpdateService/UpdateService.cs
@@ -582,7 +582,7 @@ namespace ZitiUpdateService {
ConfigureCheck();
- // for testing checkUpdateImmediately();
+ checkUpdateImmediately();
try {
dataClient.ConnectAsync().Wait();
@@ -668,22 +668,13 @@ namespace ZitiUpdateService {
}
if (sender == null && e == null) {
- Logger.Info("package is in {0} - moving to install phase", fileDestination);
-
- if (!check.HashIsValid(updateFolder, filename)) {
- Logger.Warn("The file was downloaded but the hash is not valid. The file will be removed: {0}", fileDestination);
- File.Delete(fileDestination);
- semaphore.Release();
- return;
- }
- Logger.Debug("downloaded file hash was correct. update can continue.");
- new SignedFileValidator(fileDestination).Verify();
-
- installZDE(fileDestination);
+ installZDE(fileDestination, filename);
} else {
Checkers.ZDEInstallerInfo info = check.GetZDEInstallerInfo(fileDestination);
- if (info.IsCritical && _installationReminder == null) {
+ if (info.IsCritical) {
+ installZDE(fileDestination, filename);
+ } else if (!info.IsCritical && _installationReminder == null) {
// Timer for installation reminder
var installationReminderInterval = ConfigurationManager.AppSettings.Get("InstallationReminder");
var instInt = TimeSpan.Zero;
@@ -698,6 +689,10 @@ namespace ZitiUpdateService {
_installationReminder = new CustomTimer(callback, state, instInt, instInt);
state._timer = _installationReminder;
Logger.Info("Installation reminder for ZDE version {0} is set to {1}", info.Version, instInt);
+
+ info.TimeRemaining = _installationReminder.DueTime.TotalMilliseconds / 1000; // converting to seconds
+ info.InstallTime = DateTime.Now.AddMilliseconds(_installationReminder.DueTime.TotalMilliseconds);
+ NotifyInstallationUpdates(info, 0, "");
}
if (_installationReminder != null) {
@@ -705,9 +700,6 @@ namespace ZitiUpdateService {
info.InstallTime = DateTime.Now.AddMilliseconds(_installationReminder.DueTime.TotalMilliseconds);
Logger.Info("Installation of ZDE version {0} will be initiated in {1} seconds, approximately at {2}", info.Version, info.TimeRemaining, info.InstallTime);
}
- if (info.Version != null) {
- NotifyInstallationUpdates(info, 0, "");
- }
}
} catch (Exception ex) {
try {
@@ -724,7 +716,18 @@ namespace ZitiUpdateService {
semaphore.Release();
}
- private void installZDE(string fileDestination) {
+ private void installZDE(string fileDestination, string filename) {
+ Logger.Info("package is in {0} - moving to install phase", fileDestination);
+
+ if (!check.HashIsValid(updateFolder, filename)) {
+ Logger.Warn("The file was downloaded but the hash is not valid. The file will be removed: {0}", fileDestination);
+ File.Delete(fileDestination);
+ semaphore.Release();
+ return;
+ }
+ Logger.Debug("downloaded file hash was correct. update can continue.");
+ new SignedFileValidator(fileDestination).Verify();
+
try {
StopZiti();
StopUI().Wait();
diff --git a/ZitiUpdateService/checkers/GithubCheck.cs b/ZitiUpdateService/checkers/GithubCheck.cs
index 0d2b215b..a1b495e4 100644
--- a/ZitiUpdateService/checkers/GithubCheck.cs
+++ b/ZitiUpdateService/checkers/GithubCheck.cs
@@ -181,10 +181,7 @@ namespace ZitiUpdateService.Checkers {
} else {
Logger.Debug("Comparing Version {0}, with current {1}", info.Version.ToString(), assemblyVersion.ToString());
- if (info.Version.Build - assemblyVersion.Build >= 5) {
- info.IsCritical = true;
- Logger.Info("ZDEInstaller is marked as critical because the client is behind 5 updates");
- } else if ((info.Version.Major - assemblyVersion.Major >= 1) || (info.Version.Minor - assemblyVersion.Minor >= 1)) {
+ if ((info.Version.Major - assemblyVersion.Major >= 1) || (info.Version.Minor - assemblyVersion.Minor >= 1)) {
info.IsCritical = true;
Logger.Info("ZDEInstaller is marked as critical because the major/minor version has changed");
} else {
diff --git a/release-notes.md b/release-notes.md
index 570166ff..8913fceb 100644
--- a/release-notes.md
+++ b/release-notes.md
@@ -4,14 +4,32 @@
* none
## Other changes:
-* [#278](https://github.com/openziti/desktop-edge-win/issues/278) inform the user an update is available before automatically updating. So user can manually install the latest version anytime within 1 week after the release is published. If a major/minor version has changed or if the client is missing 5 updates, then the auto installation will start in 4 hours. If the user does not initiate manual installation within the given time, a warning will be displayed for 4 hours and ZDE will auto update.
+* [#278](https://github.com/openziti/desktop-edge-win/issues/278) inform the user an update is available before automatically updating. So user can manually install the latest version anytime within 1 week after the release is published. If a major/minor version has changed or if the client is missing 5 updates, then the auto installation will start immediately. If the user does not initiate manual installation within the given time, a warning will be displayed for 2 hours and ZDE will auto update.
+* mfa timeout process
+
+## Other changes:
+* none
## Bugs fixed:
* none
## Dependency Updates
+* updated c-sdk to v0.25.2, updated t-sdk to v0.15.9
+
+# Release 1.9.10
+
+## What's New
* none
+## Other changes:
+* none
+
+## Bugs fixed:
+* none
+
+## Dependency Updates
+* wintun updated to 0.12
+
# Release 1.9.9
## What's New
diff --git a/service/build.bat b/service/build.bat
index cfaa6cd2..799c976e 100644
--- a/service/build.bat
+++ b/service/build.bat
@@ -14,12 +14,12 @@ REM See the License for the specific language governing permissions and
REM limitations under the License.
REM
SET REPO_URL=https://github.com/openziti/ziti-tunnel-sdk-c.git
-SET ZITI_TUNNEL_REPO_BRANCH=v0.15.8
+SET ZITI_TUNNEL_REPO_BRANCH=v0.15.10
REM override the c sdk used in the build - leave blank for the same as specified in the tunneler sdk
SET ZITI_SDK_C_BRANCH=
REM the number of TCP connections the tunneler sdk can have at any one time
SET TCP_MAX_CONNECTIONS=256
-SET WINTUN_DL_URL=https://www.wintun.net/builds/wintun-0.11.zip
+SET WINTUN_DL_URL=https://www.wintun.net/builds/wintun-0.12.zip
set SVC_ROOT_DIR=%~dp0
set CURDIR=%CD%
diff --git a/service/cziti/ctun.go b/service/cziti/ctun.go
index 09c082cd..62b1508b 100644
--- a/service/cziti/ctun.go
+++ b/service/cziti/ctun.go
@@ -45,6 +45,7 @@ void readIdle(uv_prepare_t *idler);
void call_on_packet(void *packet, ssize_t len, packet_cb cb, void *ctx);
void remove_intercepts(uv_async_t *handle);
+void add_intercepts(uv_async_t *handle);
void free_async(uv_handle_t* timer);
dns_manager* get_dns_mgr_from_c();
@@ -242,26 +243,42 @@ func (t *tunnel) runWriteLoop() {
}
}
-type RemoveWG struct {
+type TunnelerActionWaitGroup struct {
Wg *sync.WaitGroup
Czsvc *ZService
}
-func RemoveIntercept(rwg *RemoveWG) {
+func RemoveIntercept(removeWaitGroup *TunnelerActionWaitGroup) {
async := (*C.uv_async_t)(C.malloc(C.sizeof_uv_async_t))
- async.data = unsafe.Pointer(rwg)
+ async.data = unsafe.Pointer(removeWaitGroup)
C.uv_async_init(_impl.libuvCtx.l, async, C.uv_async_cb(C.remove_intercepts))
C.uv_async_send((*C.uv_async_t)(unsafe.Pointer(async)))
}
+func AddIntercept(addWaitGroup *TunnelerActionWaitGroup) {
+ async := (*C.uv_async_t)(C.malloc(C.sizeof_uv_async_t))
+ async.data = unsafe.Pointer(addWaitGroup)
+ C.uv_async_init(_impl.libuvCtx.l, async, C.uv_async_cb(C.add_intercepts))
+ C.uv_async_send((*C.uv_async_t)(unsafe.Pointer(async)))
+}
+
//export remove_intercepts
func remove_intercepts(async *C.uv_async_t) {
- rwg := (*RemoveWG)(async.data)
- cSvcName := C.CString(rwg.Czsvc.Name)
+ removeWaitGroup := (*TunnelerActionWaitGroup)(async.data)
+ cSvcName := C.CString(removeWaitGroup.Czsvc.Name)
defer C.free(unsafe.Pointer(cSvcName))
- C.ziti_tunneler_stop_intercepting(theTun.tunCtx, unsafe.Pointer(rwg.Czsvc.Czctx), cSvcName)
+ C.ziti_tunneler_stop_intercepting(theTun.tunCtx, unsafe.Pointer(removeWaitGroup.Czsvc.Czctx), cSvcName)
C.uv_close((*C.uv_handle_t)(unsafe.Pointer(async)), C.uv_close_cb(C.free_async))
- rwg.Wg.Done()
+ removeWaitGroup.Wg.Done()
+}
+
+//export add_intercepts
+func add_intercepts(async *C.uv_async_t) {
+ addWaitGroup := (*TunnelerActionWaitGroup)(async.data)
+
+ C.ziti_sdk_c_on_service(addWaitGroup.Czsvc.Czctx, addWaitGroup.Czsvc.Czsvc, C.ZITI_OK, unsafe.Pointer(theTun.tunCtx))
+ C.uv_close((*C.uv_handle_t)(unsafe.Pointer(async)), C.uv_close_cb(C.free_async))
+ addWaitGroup.Wg.Done()
}
//export apply_dns_go
diff --git a/service/cziti/dns-proxy.go b/service/cziti/dns-proxy.go
index 1209e975..fbc948c0 100644
--- a/service/cziti/dns-proxy.go
+++ b/service/cziti/dns-proxy.go
@@ -57,7 +57,7 @@ func processDNSquery(packet []byte, p *net.UDPAddr, s *net.UDPConn, ipVer int) {
query = q.Question[0]
log.Tracef("processing a dns query. type:%s, for:%s on %v. id:%v", dns.Type(query.Qtype), query.Name, p, q.Id)
} else {
- log.Warnf("DNS MSG had no question! %v", msg.String())
+ log.Warnf("discarding dns message as it contained no question! %v", msg.String())
return
}
@@ -378,7 +378,11 @@ outer:
for k, r := range reqs {
if now.After(r.exp) {
log.Debugf("a DNS request has expired - enable trace logging and reproduce this issue for more information")
- log.Tracef("expired DNS req: %s %s", dns.Type(r.req.Question[0].Qtype), r.req.Question[0].Name)
+ if len(r.req.Question) > 1 {
+ log.Tracef("expired DNS req: %s %s", dns.Type(r.req.Question[0].Qtype), r.req.Question[0].Name)
+ } else {
+ log.Tracef("expired DNS req - had no question???")
+ }
delete(reqs, k)
}
@@ -399,7 +403,7 @@ outer:
case rep := <-respChan:
reply := dns.Msg{}
if err := reply.Unpack(rep); err == nil {
- id := (uint32(reply.Id) << 16) | uint32(reply.Question[0].Qtype)
+ id := calcDnsId(&reply)
dnsReqMutex.Lock()
req, found := reqs[id]
if found {
@@ -428,7 +432,7 @@ outer:
log.Debug("Upstream DNS proxy loop begins")
for {
pr := <-proxiedRequests
- id := (uint32(pr.req.Id) << 16) | uint32(pr.req.Question[0].Qtype)
+ id := calcDnsId(pr.req)
dnsReqMutex.Lock()
reqs[id] = pr
dnsReqMutex.Unlock()
@@ -450,6 +454,13 @@ outer:
log.Tracef("Proxied request sent to %v from ipv%d listener", proxy.RemoteAddr(), pr.ipVer)
}
}
-
+ }
+}
+
+func calcDnsId(req *dns.Msg) uint32 {
+ if len(req.Question) > 0 {
+ return (uint32(req.Id) << 16) | uint32(req.Question[0].Qtype)
+ } else {
+ return 0 //this shouldn't be happening - yet a user hit this situation
}
}
diff --git a/service/cziti/mfa.c b/service/cziti/mfa.c
deleted file mode 100644
index 1a8d7f02..00000000
--- a/service/cziti/mfa.c
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.
- *
- */
-
-#include "sdk.h"
-#include
-#include
-
-void ziti_mfa_auth_request(ziti_ar_mfa_cb response_cb, ziti_context ztx, void *mfa_ctx, char *code, ziti_ar_mfa_status_cb auth_response, char *fingerprint) {
- response_cb(ztx, mfa_ctx, code, auth_response, fingerprint);
-}
\ No newline at end of file
diff --git a/service/cziti/mfa.go b/service/cziti/mfa.go
index ee8ab036..cce2c9fb 100644
--- a/service/cziti/mfa.go
+++ b/service/cziti/mfa.go
@@ -47,26 +47,6 @@ type mfaCodes struct {
var emptyCodes []string
var mfaAuthResults = make(chan string)
-//export ziti_aq_mfa_cb_go
-func ziti_aq_mfa_cb_go(ztx C.ziti_context, mfa_ctx unsafe.Pointer, aq_mfa *C.ziti_auth_query_mfa, response_cb C.ziti_ar_mfa_cb) {
- appCtx := C.ziti_app_ctx(ztx)
- if appCtx != C.NULL {
- log.Debugf("mfa requested for ziti context %p.", ztx)
- zid := (*ZIdentity)(appCtx)
- mfa := &Mfa{
- mfaContext: mfa_ctx,
- authQuery: aq_mfa,
- responseCb: response_cb,
- }
- zid.mfa = mfa
- zid.MfaNeeded = true
- zid.MfaEnabled = true
- log.Debugf("mfa enabled/needed set to true for ziti context [%p]. Identity name:%s [fingerprint: %s]", zid, zid.Name, zid.Fingerprint)
- } else {
- log.Debugf("mfa requested for ziti context/mfa context [%p, %p] but the context was NOT found in the map. This is unexpected. Please report.", ztx, mfa_ctx)
- }
-}
-
func EnableMFA(id *ZIdentity) {
C.ziti_mfa_enroll(id.czctx, C.ziti_mfa_cb(C.ziti_mfa_enroll_cb_go), unsafe.Pointer(C.CString(id.Fingerprint)))
}
@@ -99,6 +79,7 @@ func ziti_mfa_enroll_cb_go(_ C.ziti_context, status C.int, enrollment *C.ziti_mf
}
}
+// requires that the identity already be fully authenticated and used specifically for enrollment
func VerifyMFA(id *ZIdentity, code string) {
ccode := C.CString(code)
defer C.free(unsafe.Pointer(ccode))
@@ -157,6 +138,53 @@ func ReturnMfaCodes(id *ZIdentity, code string) ([]string, error) {
}
}
+// used when an identity is partially authenticated and waiting on 2fa, i.e. for authentication and for timeouts
+func AuthMFA(id *ZIdentity, code string) error {
+ ccode := C.CString(code)
+ defer C.free(unsafe.Pointer(ccode))
+
+ log.Tracef("authenticating MFA for fingerprint: %s using code: %s", id.Fingerprint, code)
+ C.ziti_mfa_auth(id.czctx, ccode, C.ziti_mfa_cb(C.ziti_auth_mfa_status_cb_go), unsafe.Pointer(C.CString(id.Fingerprint)))
+ authResult := strings.TrimSpace(<-mfaAuthResults)
+
+ if authResult == "" {
+ id.MfaEnabled = true
+ id.MfaNeeded = false
+ return nil
+ }
+ return fmt.Errorf("error in authMFA: %v", authResult)
+}
+
+//export ziti_auth_mfa_status_cb_go
+func ziti_auth_mfa_status_cb_go(ztx C.ziti_context, status C.int, cFingerprint *C.char) {
+ defer C.free(unsafe.Pointer(cFingerprint))
+ fp := C.GoString(cFingerprint)
+
+ log.Debugf("ziti_auth_mfa_status_cb_go called for %s. status: %d for ", fp, int(status))
+ var m = dto.MfaEvent{
+ ActionEvent: dto.MFAAuthenticationEvent,
+ Fingerprint: fp,
+ Successful: false,
+ RecoveryCodes: nil,
+ }
+
+ if status != C.ZITI_OK {
+ e := C.ziti_errorstr(status)
+ ego := C.GoString(e)
+ log.Errorf("Error encounted when authenticating 2f mfa: %v", ego)
+ m.Error = ego
+ mfaAuthResults <- ego
+ } else {
+ log.Infof("Identity with fingerprint %s has successfully authenticated MFA", fp)
+ m.Successful = true
+ goapi.UpdateMfa(fp, true, false)
+ mfaAuthResults <- ""
+ }
+
+ log.Debugf("sending ziti_mfa_auth response back to UI for %s. verified: %t. error: %s", fp, m.Successful, m.Error)
+ goapi.BroadcastEvent(m)
+}
+
//export ziti_mfa_recovery_codes_cb_return
func ziti_mfa_recovery_codes_cb_return(_ C.ziti_context, status C.int, recoveryCodes **C.char, cFingerprint *C.char) {
fp := C.GoString(cFingerprint)
@@ -237,48 +265,6 @@ func populateStringSlice(c_char_array **C.char) []string {
return strs
}
-func AuthMFA(id *ZIdentity, code string) error {
- if id.mfa == nil {
- log.Warnf("AuthMFA called but mfa is nil. This usually is because AuthMFA is called from an unenrolled MFA endpoint or the endpoint has already been auth'ed")
- return fmt.Errorf("identity has not authenticated yet")
- }
- if id.mfa.responseCb == nil {
- log.Warnf("AuthMFA called but response cb is nil. This usually is because the session is already validiated. returning true from AuthMFA")
- return nil
- }
-
- ccode := C.CString(code)
- defer C.free(unsafe.Pointer(ccode))
-
- cfp := C.CString(id.Fingerprint)
- defer C.free(unsafe.Pointer(cfp))
- log.Debugf("beginning authentication for fingerprint %s with code %s", id.Fingerprint, code)
- C.ziti_mfa_auth_request(id.mfa.responseCb, id.czctx, id.mfa.mfaContext, ccode, C.ziti_ar_mfa_status_cb(C.ziti_ar_mfa_status_cb_go), cfp)
- authResult := strings.TrimSpace(<-mfaAuthResults)
-
- if authResult == "" {
- id.mfa.responseCb = nil
- id.MfaNeeded = false
- return nil
- }
- return fmt.Errorf("error in authMFA: %v", authResult)
-}
-
-//export ziti_ar_mfa_status_cb_go
-func ziti_ar_mfa_status_cb_go(ztx C.ziti_context, mfa_ctx unsafe.Pointer, status C.int, cFingerprint *C.char) {
- fp := C.GoString(cFingerprint)
- log.Debugf("ziti_ar_mfa_status_cb_go called for fingerprint: %s with status %v", fp, status)
- if status == C.ZITI_OK {
- log.Infof("mfa authentication succeeded for fingerprint: %s", fp)
- mfaAuthResults <- ""
- } else {
- log.Warnf("mfa authentication failed for fingerprint: %s", fp)
- e := C.ziti_errorstr(status)
- ego := C.GoString(e)
- mfaAuthResults <- ego
- }
-}
-
func RemoveMFA(id *ZIdentity, code string) {
ccode := C.CString(code)
defer C.free(unsafe.Pointer(ccode))
@@ -314,3 +300,8 @@ 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)
}
+
+func EndpointStateChanged(id *ZIdentity, woken bool, unlocked bool) {
+ C.ziti_endpoint_state_change(id.czctx, C.bool(woken), C.bool(unlocked))
+ log.Debugf("Endpoint status changed for id %s - woken %t, unlocked %t", id.Fingerprint, woken, unlocked)
+}
diff --git a/service/cziti/sdk.go b/service/cziti/sdk.go
index 2f116a56..431a8743 100644
--- a/service/cziti/sdk.go
+++ b/service/cziti/sdk.go
@@ -47,12 +47,14 @@ import (
"encoding/json"
"errors"
"github.com/openziti/desktop-edge-win/service/ziti-tunnel/api"
+ "github.com/openziti/desktop-edge-win/service/ziti-tunnel/constants"
"github.com/openziti/desktop-edge-win/service/ziti-tunnel/dto"
"github.com/openziti/desktop-edge-win/service/ziti-tunnel/util/logging"
"net"
"os"
"strings"
"sync"
+ "time"
"unsafe"
)
@@ -73,11 +75,29 @@ type ServiceChange struct {
ZitiContext *ZIdentity
}
type BulkServiceChange struct {
- Fingerprint string
- HostnamesToAdd map[string]bool
- HostnamesToRemove map[string]bool
- ServicesToRemove []*dto.Service
- ServicesToAdd []*dto.Service
+ Fingerprint string
+ HostnamesToAdd map[string]bool
+ HostnamesToRemove map[string]bool
+ ServicesToRemove []*dto.Service
+ ServicesToAdd []*dto.Service
+ MfaMinTimeout int32
+ MfaMaxTimeout int32
+ MfaLastUpdatedTime time.Time
+}
+
+type NotificationMessage struct {
+ IdentityName string
+ Fingerprint string
+ Message string
+ MfaMinimumTimeout int32
+ MfaMaximumTimeout int32
+ MfaTimeDuration int
+ Severity string
+}
+
+type TunnelNotificationEvent struct {
+ Op string
+ Notification []NotificationMessage
}
var _impl sdk
@@ -109,35 +129,34 @@ func (inst *sdk) run(loglevel int) {
C.libuv_run(inst.libuvCtx)
}
+type RefreshRequiredCheck func(int32, int32) bool
+
type ZService struct {
Name string
Id string
Service *dto.Service
+ Czsvc *C.ziti_service
Czctx C.ziti_context
}
type ZIdentity struct {
- Options *C.ziti_options
- czctx C.ziti_context
- czid *C.ziti_identity
- status int
- statusErr error
- Loaded bool
- Name string
- Version string
- Services sync.Map
- Fingerprint string
- Active bool
- StatusChanges func(int)
- MfaNeeded bool
- MfaEnabled bool
- mfa *Mfa
-}
-
-type Mfa struct {
- mfaContext unsafe.Pointer
- authQuery *C.ziti_auth_query_mfa
- responseCb C.ziti_ar_mfa_cb
+ Options *C.ziti_options
+ czctx C.ziti_context
+ czid *C.ziti_identity
+ status int
+ statusErr error
+ Loaded bool
+ Name string
+ Version string
+ Services sync.Map
+ Fingerprint string
+ Active bool
+ StatusChanges func(int)
+ MfaNeeded bool
+ MfaEnabled bool
+ MfaMinTimeout int32
+ MfaMaxTimeout int32
+ MfaLastUpdatedTime time.Time
}
func NewZid(statusChange func(int)) *ZIdentity {
@@ -145,6 +164,9 @@ func NewZid(statusChange func(int)) *ZIdentity {
zid.Services = sync.Map{}
zid.Options = (*C.ziti_options)(C.calloc(1, C.sizeof_ziti_options))
zid.StatusChanges = statusChange
+ zid.MfaMinTimeout = -1
+ zid.MfaMaxTimeout = -1
+ zid.MfaLastUpdatedTime = time.Now()
return zid
}
@@ -224,6 +246,34 @@ func (zid *ZIdentity) Shutdown() {
C.uv_async_send((*C.uv_async_t)(unsafe.Pointer(async)))
}
+func (zid *ZIdentity) MfaRefreshNeeded() bool {
+ return !(zid.MfaMinTimeout == -1 && zid.MfaMaxTimeout == -1)
+}
+
+func (zid *ZIdentity) GetRemainingTime(timeout int32) int32 {
+ var remainingTimeout int32
+ if (timeout - int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) <= 0 {
+ remainingTimeout = 0
+ } else {
+ remainingTimeout = timeout - int32(time.Since(zid.MfaLastUpdatedTime).Seconds())
+ }
+ return remainingTimeout
+}
+
+func (zid *ZIdentity) GetMFAState(interval int32) int {
+ var mfaState int
+ if (zid.MfaMaxTimeout > -1) && (zid.MfaMaxTimeout-int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < 0 {
+ mfaState = constants.MfaAllSvcTimeout
+ } else if (zid.MfaMinTimeout - int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < 0 {
+ mfaState = constants.MfaFewSvcTimeout
+ } else if (zid.MfaMinTimeout - int32(time.Since(zid.MfaLastUpdatedTime).Seconds())) < interval {
+ mfaState = constants.MfaNearingTimeout
+ } else {
+ mfaState = -1
+ }
+ return mfaState
+}
+
//export doZitiShutdown
func doZitiShutdown(async *C.uv_async_t) {
ctx := C.ziti_context(async.data)
@@ -315,6 +365,7 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, z
//if any posture query sets pass - that will grant the user access to that service
hasAccess := false
+ timeout := -1
//find all posture checks sets...
for setIdx := 0; true; setIdx++ {
@@ -344,17 +395,28 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, z
var pcId string
pcId = C.GoString(pq.id)
+ log.Infof("Posture query %s, timeout %d", C.GoString(pq.id), int(pq.timeout))
_, found := pcIds[pcId]
if found {
log.Tracef("posture check with id %s already in failing posture check map", pcId)
+ for _, pc := range postureChecks {
+ if pc.Id == C.GoString(pq.id) {
+ if timeout == -1 || timeout > int(pq.timeout) {
+ timeout = int(pq.timeout)
+ }
+ break
+ }
+ }
} else {
pcIds[C.GoString(pq.id)] = false
pc := dto.PostureCheck{
IsPassing: bool(pq.is_passing),
QueryType: C.GoString(pq.query_type),
Id: pcId,
+ Timeout: int(pq.timeout),
}
+ timeout = pc.Timeout
postureChecks = append(postureChecks, pc)
}
@@ -370,11 +432,13 @@ func serviceCB(ziti_ctx C.ziti_context, service *C.ziti_service, status C.int, z
OwnsIntercept: true,
PostureChecks: postureChecks,
IsAccessable: hasAccess,
+ Timeout: int32(timeout),
}
added := ZService{
Name: name,
Id: svcId,
Service: svc,
+ Czsvc: service,
Czctx: ziti_ctx,
}
zid.Services.Store(svcId, &added)
@@ -506,6 +570,7 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) {
if unsafe.Pointer(added) == C.NULL {
break
}
+
svcToAdd := serviceCB(ztx, added, C.ZITI_OK, zid)
if svcToAdd != nil {
addAddys := svcToAdd.Addresses
@@ -517,13 +582,45 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) {
servicesToAdd = append(servicesToAdd, svcToAdd)
}
}
+ var minimumTimeout int32 = -1
+ var maximumTimeout int32 = -1
+ noTimeoutSvc := false
+ if len(servicesToAdd) > 0 {
+ zid.Services.Range(func(key interface{}, value interface{}) bool {
+ //string, ZService
+ val := value.(*ZService)
+
+ if val.Service.Timeout >= 0 {
+ if minimumTimeout == -1 || minimumTimeout > val.Service.Timeout {
+ minimumTimeout = val.Service.Timeout
+ }
+ if maximumTimeout == -1 || maximumTimeout < val.Service.Timeout {
+ maximumTimeout = val.Service.Timeout
+ }
+ } else {
+ noTimeoutSvc = true
+ }
+
+ return true
+
+ })
+ if noTimeoutSvc {
+ maximumTimeout = -1
+ }
+ }
+
+ zid.MfaMinTimeout = minimumTimeout
+ zid.MfaMaxTimeout = maximumTimeout
svcChange := BulkServiceChange{
- Fingerprint: zid.Fingerprint,
- HostnamesToAdd: hostnamesToAdd,
- HostnamesToRemove: hostnamesToRemove,
- ServicesToAdd: servicesToAdd,
- ServicesToRemove: servicesToRemove,
+ Fingerprint: zid.Fingerprint,
+ HostnamesToAdd: hostnamesToAdd,
+ HostnamesToRemove: hostnamesToRemove,
+ ServicesToAdd: servicesToAdd,
+ ServicesToRemove: servicesToRemove,
+ MfaMinTimeout: minimumTimeout,
+ MfaMaxTimeout: maximumTimeout,
+ MfaLastUpdatedTime: time.Now(),
}
if len(BulkServiceChanges) == cap(BulkServiceChanges) {
@@ -531,6 +628,33 @@ func eventCB(ztx C.ziti_context, event *C.ziti_event_t) {
} else {
BulkServiceChanges <- svcChange
}
+ case C.ZitiMfaAuthEvent:
+ zid := (*ZIdentity)(appCtx)
+ log.Debugf("mfa auth event for finger print %s", zid.Fingerprint)
+ zid.MfaNeeded = true
+ zid.MfaEnabled = true
+
+ if zid.Fingerprint != "" {
+ var id = dto.Identity{
+ Name: zid.Name,
+ FingerPrint: zid.Fingerprint,
+ Active: zid.Active,
+ ControllerVersion: zid.Version,
+ Status: "",
+ MfaNeeded: true,
+ MfaEnabled: true,
+ Tags: nil,
+ }
+
+ var m = dto.IdentityEvent{
+ ActionEvent: dto.IDENTITY_ADDED,
+ Id: id,
+ }
+ goapi.BroadcastEvent(m)
+ log.Debugf("mfa auth event set enabled/needed to true for ziti context [%p]. Identity name:%s [fingerprint: %s]", zid, zid.Name, zid.Fingerprint)
+ } else {
+ log.Debugf("mfa auth event set received with empty finger print, ziti context [%p]", zid)
+ }
default:
log.Infof("event %d not handled", event._type)
}
@@ -576,11 +700,9 @@ func LoadZiti(zid *ZIdentity, cfg string, refreshInterval int) {
zid.Options.pq_os_cb = C.ziti_pq_os_cb(C.ziti_pq_os_go)
zid.Options.pq_process_cb = C.ziti_pq_process_cb(C.ziti_pq_process_go)
- zid.Options.events = C.ZitiContextEvent | C.ZitiServiceEvent | C.ZitiRouterEvent
+ zid.Options.events = C.ZitiContextEvent | C.ZitiServiceEvent | C.ZitiRouterEvent | C.ZitiMfaAuthEvent
zid.Options.event_cb = C.ziti_event_cb(C.eventCB)
- zid.Options.aq_mfa_cb = C.ziti_aq_mfa_cb(C.ziti_aq_mfa_cb_go)
-
ptr := unsafe.Pointer(zid)
zid.Options.app_ctx = ptr
diff --git a/service/cziti/sdk.h b/service/cziti/sdk.h
index d68ead6a..d57ca5e3 100644
--- a/service/cziti/sdk.h
+++ b/service/cziti/sdk.h
@@ -75,15 +75,14 @@ ziti_service* ziti_service_array_get(ziti_service_array arr, int idx);
void ziti_dump_go(char *msg);
//declare all mfa callbacks
-void ziti_aq_mfa_cb_go(ziti_context ztx, void *mfa_ctx, ziti_auth_query_mfa *aq_mfa, ziti_ar_mfa_cb response_cb);
void ziti_mfa_enroll_cb_go(ziti_context ztx, int status, ziti_mfa_enrollment *mfa_enrollment, char *fingerprint);
void ziti_mfa_cb_verify_go(ziti_context ztx, int status, char *fingerprint);
void ziti_mfa_cb_remove_go(ziti_context ztx, int status, char *fingerprint);
void ziti_mfa_recovery_codes_cb_return(ziti_context ztx, int status, char **recovery_codes, char *fingerprint);
void ziti_mfa_recovery_codes_cb_generate(ziti_context ztx, int status, char **recovery_codes, char *fingerprint);
-void ziti_mfa_auth_request(ziti_ar_mfa_cb response_cb, ziti_context ztx, void *mfa_ctx, char *code, ziti_ar_mfa_status_cb auth_response, char *fingerprint);
void ziti_mfa_ar_cb(ziti_context ztx, void *mfa_ctx, int status);
void ziti_ar_mfa_status_cb_go(ziti_context ztx, void *mfa_ctx, int status, char *fingerprint);
+void ziti_auth_mfa_status_cb_go(ziti_context ztx, int status, char *fingerprint);
ziti_posture_query_set* posture_query_set_get(ziti_posture_query_set_array arr, int idx);
ziti_posture_query* posture_queries_get(ziti_posture_query_array arr, int idx);
diff --git a/service/windns/windns.go b/service/windns/windns.go
index 0d38d8f6..701f45a7 100644
--- a/service/windns/windns.go
+++ b/service/windns/windns.go
@@ -307,4 +307,4 @@ Get-ItemProperty -Path $key | where {$_.ProfileName -match "Ziti.*"} | Remove-It
log.Errorf("ERROR Cleaning up the Network Adapter profiles: %v", err)
}
-}
\ No newline at end of file
+}
diff --git a/service/ziti-tunnel/constants/consts.go b/service/ziti-tunnel/constants/consts.go
index cc1c95ea..4496b4f1 100644
--- a/service/ziti-tunnel/constants/consts.go
+++ b/service/ziti-tunnel/constants/consts.go
@@ -18,8 +18,14 @@
package constants
const (
- Ipv4ip = "100.64.0.1"
- Ipv4MaxMask = 8
- Ipv4MinMask = 16
- Ipv4DefaultMask = 10
+ Ipv4ip = "100.64.0.1"
+ Ipv4MaxMask = 10
+ Ipv4MinMask = 16
+ Ipv4DefaultMask = 10
+ MinimumFrequency = 5 // minimum notification window
+ MaximumFrequency = 20 // maximum notification window
+
+ MfaAllSvcTimeout = 0
+ MfaFewSvcTimeout = 1
+ MfaNearingTimeout = 2
)
diff --git a/service/ziti-tunnel/dto/dtos.go b/service/ziti-tunnel/dto/dtos.go
index c8930d43..20c88f2c 100644
--- a/service/ziti-tunnel/dto/dtos.go
+++ b/service/ziti-tunnel/dto/dtos.go
@@ -19,6 +19,7 @@ package dto
import (
"log"
+ "time"
"github.com/openziti/desktop-edge-win/service/ziti-tunnel/config"
idcfg "github.com/openziti/sdk-golang/ziti/config"
@@ -39,6 +40,7 @@ type Service struct {
OwnsIntercept bool
PostureChecks []PostureCheck
IsAccessable bool
+ Timeout int32
}
type Address struct {
@@ -57,6 +59,7 @@ type PostureCheck struct {
IsPassing bool
QueryType string
Id string
+ Timeout int
}
type ServiceOwner struct {
@@ -68,17 +71,20 @@ type HostContext struct {
}
type Identity struct {
- Name string
- FingerPrint string
- Active bool
- Config idcfg.Config
- ControllerVersion string
- Status string
- MfaEnabled bool
- MfaNeeded bool
- Services []*Service `json:",omitempty"`
- Metrics *Metrics `json:",omitempty"`
- Tags []string `json:",omitempty"`
+ Name string
+ FingerPrint string
+ Active bool
+ Config idcfg.Config
+ ControllerVersion string
+ Status string
+ MfaEnabled bool
+ MfaNeeded bool
+ Services []*Service `json:",omitempty"`
+ Metrics *Metrics `json:",omitempty"`
+ Tags []string `json:",omitempty"`
+ MfaMinTimeout int32
+ MfaMaxTimeout int32
+ MfaLastUpdatedTime time.Time
}
type Metrics struct {
Up int64
@@ -110,16 +116,17 @@ func (id *Identity) Path() string {
}
type TunnelStatus struct {
- Active bool
- Duration int64
- Identities []*Identity
- IpInfo *TunIpInfo `json:"IpInfo,omitempty"`
- LogLevel string
- ServiceVersion ServiceVersion
- TunIpv4 string
- TunIpv4Mask int
- Status string
- AddDns bool
+ Active bool
+ Duration int64
+ Identities []*Identity
+ IpInfo *TunIpInfo `json:"IpInfo,omitempty"`
+ LogLevel string
+ ServiceVersion ServiceVersion
+ TunIpv4 string
+ TunIpv4Mask int
+ Status string
+ AddDns bool
+ NotificationFrequency int
}
type ServiceVersion struct {
diff --git a/service/ziti-tunnel/dto/events.go b/service/ziti-tunnel/dto/events.go
index e2e43379..0fb64b08 100644
--- a/service/ziti-tunnel/dto/events.go
+++ b/service/ziti-tunnel/dto/events.go
@@ -17,13 +17,15 @@
package dto
const (
- ADDED = "added"
- BULK = "bulk"
- REMOVED = "removed"
- ERROR = "error"
- UPDATED = "updated"
- CHANGED = "changed"
- NORMAL = "Normal"
+ ADDED = "added"
+ BULK = "bulk"
+ REMOVED = "removed"
+ ERROR = "error"
+ UPDATED = "updated"
+ CHANGED = "changed"
+ NORMAL = "Normal"
+ CONNECTED = "connected"
+ DISCONNECTED = "disconnected"
SERVICE_OP = "service"
BULK_SERVICE_OP = "bulkservice"
@@ -37,6 +39,7 @@ const (
MFAEnrollmentRemovedAction = "enrollment_remove"
MFA_AUTH_CHALLENGE_ACTION = "auth_challenge"
+ MFAAuthenticationAction = "mfa_auth_status"
)
var SERVICE_ADDED = ActionEvent{
@@ -60,6 +63,14 @@ var IDENTITY_REMOVED = ActionEvent{
StatusEvent: StatusEvent{Op: IDENTITY_OP},
Action: REMOVED,
}
+var IDENTITY_CONNECTED = ActionEvent{
+ StatusEvent: StatusEvent{Op: IDENTITY_OP},
+ Action: CONNECTED,
+}
+var IDENTITY_DISCONNECTED = ActionEvent{
+ StatusEvent: StatusEvent{Op: IDENTITY_OP},
+ Action: DISCONNECTED,
+}
var LOGLEVEL_CHANGED = ActionEvent{
StatusEvent: StatusEvent{Op: LOGLEVEL_OP},
Action: CHANGED,
@@ -96,3 +107,7 @@ var MFAAuthChallengeEvent = ActionEvent{
StatusEvent: StatusEvent{Op: MFA_OP},
Action: MFA_AUTH_CHALLENGE_ACTION,
}
+var MFAAuthenticationEvent = ActionEvent{
+ StatusEvent: StatusEvent{Op: MFA_OP},
+ Action: MFAAuthenticationAction,
+}
diff --git a/service/ziti-tunnel/service/ipc.go b/service/ziti-tunnel/service/ipc.go
index 6a4aad58..7ee17ce4 100644
--- a/service/ziti-tunnel/service/ipc.go
+++ b/service/ziti-tunnel/service/ipc.go
@@ -17,6 +17,7 @@
package service
+import "C"
import (
"bufio"
"crypto/sha1"
@@ -37,6 +38,7 @@ import (
"golang.zx2c4.com/wireguard/tun"
"io"
"io/ioutil"
+ "math"
"math/rand"
"net"
"os"
@@ -45,6 +47,7 @@ import (
"strconv"
"strings"
"sync"
+ "sync/atomic"
"time"
)
@@ -61,8 +64,9 @@ func (p *Pipes) Close() {
}
var shutdown = make(chan bool, 8) //a channel informing go routines to exit
+var notificationFrequency *time.Ticker
-func SubMain(ops chan string, changes chan<- svc.Status) error {
+func SubMain(ops chan string, changes chan<- svc.Status, winEvents <-chan WindowsEvents) error {
log.Info("============================== service begins ==============================")
windns.RemoveAllNrptRules()
// cleanup old ziti tun profiles
@@ -82,6 +86,7 @@ func SubMain(ops chan string, changes chan<- svc.Status) error {
// a channel to signal the handleEvents that initialization is complete
initialized := make(chan struct{})
+ shutdownDelay := make(chan bool)
// initialize the network interface
err := initialize(cLogLevel)
@@ -101,6 +106,32 @@ func SubMain(ops chan string, changes chan<- svc.Status) error {
//listen for services that show up
go acceptServices()
+ // listen for windows power events and call mfa auth func
+ go func() {
+ for {
+ select {
+ case wEvents := <-winEvents:
+ if wEvents.WinPowerEvent == PBT_APMRESUMESUSPEND || wEvents.WinPowerEvent == PBT_APMRESUMEAUTOMATIC {
+ log.Debugf("Received Windows Power Event in tunnel %d", wEvents.WinPowerEvent)
+ for _, id := range rts.ids {
+ cziti.EndpointStateChanged(id.CId, true, false)
+ }
+ }
+ if wEvents.WinSessionEvent == WTS_SESSION_UNLOCK {
+ log.Debugf("Received Windows Session Event (device unlocked) in tunnel %d", wEvents.WinSessionEvent)
+ for _, id := range rts.ids {
+ if id.CId != nil && id.CId.Loaded {
+ cziti.EndpointStateChanged(id.CId, false, true)
+ }
+ }
+ }
+ case <-shutdownDelay:
+ log.Tracef("Exiting windows power events loop")
+ return
+ }
+ }
+ }()
+
// open the pipe for business
pipes, err := openPipes()
if err != nil {
@@ -136,10 +167,9 @@ func SubMain(ops chan string, changes chan<- svc.Status) error {
})
// wait 1 second for the shutdown to send to clients
- shutdownDelay := make(chan bool)
go func() {
time.Sleep(1 * time.Second)
- shutdownDelay <- true
+ close(shutdownDelay)
}()
<-shutdownDelay
@@ -173,8 +203,7 @@ func SubMain(ops chan string, changes chan<- svc.Status) error {
func requestShutdown(requester string) {
log.Infof("shutdown requested by %v", requester)
- shutdown <- true // stops the metrics ticker
- shutdown <- true // stops the service change listener
+ close(shutdown) // stops the metrics ticker, service change listener and notification alert loop
}
func waitForStopRequest(ops <-chan string) {
@@ -551,6 +580,9 @@ func serveIpc(conn net.Conn) {
fingerprint := cmd.Payload["Fingerprint"].(string)
code := cmd.Payload["Code"].(string)
removeMFA(enc, fingerprint, code)
+ case "UpdateFrequency":
+ notificationFreq := cmd.Payload["NotificationFrequency"].(float64)
+ updateNotificationFrequency(enc, int(notificationFreq))
case "Debug":
dbg()
respond(enc, dto.Response{
@@ -931,16 +963,31 @@ func connectIdentity(id *Id) {
if id.CId == nil || !id.CId.Loaded {
rts.LoadIdentity(id, DEFAULT_REFRESH_INTERVAL)
+ rts.BroadcastEvent(dto.IdentityEvent{
+ ActionEvent: dto.IDENTITY_ADDED,
+ Id: id.Identity,
+ })
} else {
log.Debugf("%s[%s] is already loaded", id.Name, id.FingerPrint)
id.CId.Services.Range(func(key interface{}, value interface{}) bool {
id.Services = append(id.Services, nil)
+
+ val := value.(*cziti.ZService)
+ var wg sync.WaitGroup
+ wg.Add(1)
+ rwg := &cziti.TunnelerActionWaitGroup{
+ Wg: &wg,
+ Czsvc: val,
+ }
+ cziti.AddIntercept(rwg)
+ wg.Wait()
+
return true
})
rts.BroadcastEvent(dto.IdentityEvent{
- ActionEvent: dto.IDENTITY_ADDED,
+ ActionEvent: dto.IDENTITY_CONNECTED,
Id: id.Identity,
})
log.Infof("connecting identity completed: %s[%s] %t/%t", id.Name, id.FingerPrint, id.MfaEnabled, id.MfaNeeded)
@@ -960,7 +1007,7 @@ func disconnectIdentity(id *Id) error {
val := value.(*cziti.ZService)
var wg sync.WaitGroup
wg.Add(1)
- rwg := &cziti.RemoveWG{
+ rwg := &cziti.TunnelerActionWaitGroup{
Wg: &wg,
Czsvc: val,
}
@@ -968,6 +1015,10 @@ func disconnectIdentity(id *Id) error {
wg.Wait()
return true
})
+ rts.BroadcastEvent(dto.IdentityEvent{
+ ActionEvent: dto.IDENTITY_DISCONNECTED,
+ Id: id.Identity,
+ })
log.Infof("disconnecting identity complete: %s", id.Name)
}
} else {
@@ -1085,10 +1136,38 @@ func handleBulkServiceChange(sc cziti.BulkServiceChange) {
rts.BroadcastEvent(m)
}
+func addUnit(count int, unit string) (result string) {
+ if (count == 1) || (count == 0) {
+ result = strconv.Itoa(count) + " " + unit + " "
+ } else {
+ result = strconv.Itoa(count) + " " + unit + "s "
+ }
+ return
+}
+
+func secondsToReadableFmt(input int32) (result string) {
+ seconds := input % (60 * 60 * 24)
+ hours := math.Floor(float64(seconds) / 60 / 60)
+ seconds = input % (60 * 60)
+ minutes := math.Floor(float64(seconds) / 60)
+ seconds = input % 60
+
+ if hours > 0 {
+ result = addUnit(int(hours), "hour") + addUnit(int(minutes), "minute") + addUnit(int(seconds), "second")
+ } else if minutes > 0 {
+ result = addUnit(int(minutes), "minute") + addUnit(int(seconds), "second")
+ } else {
+ result = addUnit(int(seconds), "second")
+ }
+
+ return
+}
+
func handleEvents(isInitialized chan struct{}) {
events.run()
d := 5 * time.Second
every5s := time.NewTicker(d)
+ notificationFrequency = time.NewTicker(time.Duration(rts.state.NotificationFrequency) * time.Minute)
defer log.Debugf("exiting handleEvents. loops were set for %v", d)
<-isInitialized
@@ -1100,14 +1179,68 @@ func handleEvents(isInitialized chan struct{}) {
case <-every5s.C:
s := rts.ToMetrics()
+ // broadcast metrics
rts.BroadcastEvent(dto.MetricsEvent{
StatusEvent: dto.StatusEvent{Op: "metrics"},
Identities: s.Identities,
})
+
+ // notification message
+ case <-notificationFrequency.C:
+ cleanNotifications := make([]cziti.NotificationMessage, 0)
+ for _, id := range rts.ids {
+ if id.CId == nil || !id.CId.MfaRefreshNeeded() {
+ continue
+ }
+ notificationMessage := ""
+
+ var notificationMinTimeout int32 = 0
+ var notificationMaxTimeout int32 = -1
+ switch mfaState := id.CId.GetMFAState(int32((constants.MaximumFrequency+rts.state.NotificationFrequency)*60)); mfaState {
+ case constants.MfaAllSvcTimeout:
+ notificationMessage = fmt.Sprintf("All of the services of identity %s are timed out", id.Name)
+ case constants.MfaFewSvcTimeout:
+ notificationMessage = fmt.Sprintf("Some of the services of identity %s are timed out", id.Name)
+ case constants.MfaNearingTimeout:
+ notificationMinTimeout = id.CId.GetRemainingTime(id.CId.MfaMinTimeout)
+ notificationMessage = fmt.Sprintf("Some of the services of identity %s are timing out in %s", id.Name, secondsToReadableFmt(notificationMinTimeout))
+ default:
+ // do nothing
+ }
+ if len(notificationMessage) > 0 {
+
+ if id.CId.MfaMaxTimeout > -1 {
+ notificationMaxTimeout = id.CId.GetRemainingTime(id.CId.MfaMaxTimeout)
+ }
+ notificationMinTimeout = id.CId.GetRemainingTime(id.CId.MfaMinTimeout)
+
+ cleanNotifications = append(cleanNotifications, cziti.NotificationMessage{
+ Fingerprint: id.FingerPrint,
+ IdentityName: id.Name,
+ Severity: "major",
+ MfaMinimumTimeout: notificationMinTimeout,
+ MfaMaximumTimeout: notificationMaxTimeout,
+ Message: notificationMessage,
+ MfaTimeDuration: int(time.Since(id.CId.MfaLastUpdatedTime).Seconds()),
+ })
+ }
+ }
+
+ if len(cleanNotifications) > 0 {
+ log.Debugf("Sending notification message to UI %v", cleanNotifications)
+ rts.BroadcastEvent(cziti.TunnelNotificationEvent{
+ Op: "notification",
+ Notification: cleanNotifications,
+ })
+ }
}
}
}
+func ResetFrequency(newFrequency int) {
+ notificationFrequency.Reset(time.Duration(newFrequency) * time.Minute)
+}
+
//Removes the Config from the provided identity and returns a 'cleaned' id
func Clean(src *Id) dto.Identity {
mfaNeeded := false
@@ -1118,7 +1251,7 @@ func Clean(src *Id) dto.Identity {
mfaEnabled = src.CId.MfaEnabled
}
- log.Tracef("cleaning identity: %s %t %t", src.Name, mfaNeeded, mfaEnabled)
+ log.Tracef("cleaning identity: %s: mfaNeeded: %t mfaEnabled:%t", src.Name, mfaNeeded, mfaEnabled)
AddMetrics(src)
nid := dto.Identity{
Name: src.Name,
@@ -1138,9 +1271,35 @@ func Clean(src *Id) dto.Identity {
src.CId.Services.Range(func(key interface{}, value interface{}) bool {
//string, ZService
val := value.(*cziti.ZService)
+
+ if src.CId.MfaRefreshNeeded() && val.Service.Timeout > 0 {
+ var svcTimeout int32 = -1
+ for _, pc := range val.Service.PostureChecks {
+ if svcTimeout == -1 || svcTimeout > int32(pc.Timeout) {
+ svcTimeout = int32(pc.Timeout)
+ }
+ break
+ }
+ if (svcTimeout - int32(time.Since(src.CId.MfaLastUpdatedTime).Seconds())) < 0 {
+ atomic.StoreInt32(&val.Service.Timeout, 0)
+ } else {
+ atomic.StoreInt32(&val.Service.Timeout, svcTimeout-int32(time.Since(src.CId.MfaLastUpdatedTime).Seconds()))
+ }
+ }
+
nid.Services = append(nid.Services /*svcToDto(val)*/, val.Service)
return true
})
+ nid.MfaMinTimeout = src.CId.MfaMinTimeout
+ nid.MfaMaxTimeout = src.CId.MfaMaxTimeout
+ if !src.CId.MfaRefreshNeeded() {
+ nid.MfaLastUpdatedTime = time.Now()
+ } else {
+ nid.MfaLastUpdatedTime = src.CId.MfaLastUpdatedTime
+ if (nid.MfaMaxTimeout-int32(time.Since(nid.MfaLastUpdatedTime).Seconds())) < 0 && nid.MfaEnabled {
+ nid.MfaNeeded = true
+ }
+ }
}
nid.Config.ZtAPI = src.Config.ZtAPI
@@ -1163,6 +1322,8 @@ func authMfa(out *json.Encoder, fingerprint string, code string) {
id := rts.Find(fingerprint)
result := cziti.AuthMFA(id.CId, code)
if result == nil {
+ id.CId.MfaNeeded = false
+ id.CId.MfaLastUpdatedTime = time.Now()
respond(out, dto.Response{Message: "AuthMFA complete", Code: SUCCESS, Error: "", Payload: fingerprint})
} else {
respondWithError(out, fmt.Sprintf("AuthMFA failed. the supplied code [%s] was not valid: %s", code, result), 1, result)
@@ -1199,3 +1360,18 @@ func sendIdentityAndNotifyUI(enc *json.Encoder, fingerprint string) {
resp := dto.Response{Message: "", Code: ERROR, Error: "Could not find id matching fingerprint " + fingerprint, Payload: ""}
respond(enc, resp)
}
+
+func updateNotificationFrequency(out *json.Encoder, notificationFreq int) {
+
+ if notificationFreq != rts.state.NotificationFrequency {
+ err := rts.UpdateNotificationFrequency(notificationFreq)
+ if err != nil {
+ respondWithError(out, "Could not set notification frequency", UNKNOWN_ERROR, err)
+ return
+ }
+
+ ResetFrequency(notificationFreq)
+ }
+ respond(out, dto.Response{Message: "Notification frequency is set", Code: SUCCESS, Error: "", Payload: ""})
+
+}
diff --git a/service/ziti-tunnel/service/pkg-vars.go b/service/ziti-tunnel/service/pkg-vars.go
index fdd49633..c4488fb0 100644
--- a/service/ziti-tunnel/service/pkg-vars.go
+++ b/service/ziti-tunnel/service/pkg-vars.go
@@ -64,7 +64,7 @@ const (
DEFAULT_REFRESH_INTERVAL = 10
- cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue | svc.AcceptPowerEvent
+ cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue | svc.AcceptPowerEvent | svc.AcceptSessionChange
InformationEvent = 0 //1
ContinueEvent = 0 //2
@@ -81,6 +81,10 @@ const (
PBT_APMPOWERSTATUSCHANGE = 10 // Power Status Change
PBT_APMRESUMEAUTOMATIC = 18 // Resume Automatic
+ // windows session changes microsoft docs https://docs.microsoft.com/en-us/windows/win32/termserv/wm-wtssession-change
+ WTS_SESSION_LOCK = 7
+ WTS_SESSION_UNLOCK = 8
+
// This is the name you will use for the NET START command
SvcStartName = "ziti"
diff --git a/service/ziti-tunnel/service/service.go b/service/ziti-tunnel/service/service.go
index f9ce3fbe..45d47ca0 100644
--- a/service/ziti-tunnel/service/service.go
+++ b/service/ziti-tunnel/service/service.go
@@ -41,8 +41,9 @@ func (m *zitiService) Execute(args []string, r <-chan svc.ChangeRequest, changes
}
control := make(chan string)
mainLoop := make(chan struct{})
+ winEvents := make(chan WindowsEvents)
go func() {
- err := SubMain(control, changes)
+ err := SubMain(control, changes, winEvents)
if err != nil {
log.Errorf("the main loop exited with an unexpected error: %v", err)
}
@@ -82,6 +83,7 @@ loop:
if c.EventType == PBT_APMRESUMESUSPEND || c.EventType == PBT_APMPOWERSTATUSCHANGE || c.EventType == PBT_APMRESUMEAUTOMATIC {
// more than one event may be generated
powerEvent = fmt.Sprintf("Power event received - resumed, Event Type - %d", c.EventType)
+ winEvents <- WindowsEvents{WinPowerEvent: c.EventType, WinSessionEvent: 0}
} else if c.EventType == PBT_APMSUSPEND {
powerEvent = fmt.Sprintf("Power event received - suspend, Event Type - %d", c.EventType)
} else {
@@ -89,6 +91,18 @@ loop:
}
_ = logging.Elog.Info(InformationEvent, powerEvent)
log.Infof(powerEvent)
+ case svc.SessionChange:
+ var sessionChangedEvent string
+ if c.EventType == WTS_SESSION_UNLOCK {
+ sessionChangedEvent = fmt.Sprintf("Windows session changed event received - unlocked device, Event Type %d", c.EventType)
+ winEvents <- WindowsEvents{WinPowerEvent: 0, WinSessionEvent: c.EventType}
+ } else if c.EventType == WTS_SESSION_LOCK {
+ sessionChangedEvent = fmt.Sprintf("Windows session changed event received - locked device, Event Type %d", c.EventType)
+ } else {
+ sessionChangedEvent = fmt.Sprintf("Windows session changed unknown event received - Event Type %d", c.EventType)
+ }
+ _ = logging.Elog.Info(InformationEvent, sessionChangedEvent)
+ log.Infof(sessionChangedEvent)
default:
_ = logging.Elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
}
diff --git a/service/ziti-tunnel/service/state.go b/service/ziti-tunnel/service/state.go
index 0157a2c5..d53419b3 100644
--- a/service/ziti-tunnel/service/state.go
+++ b/service/ziti-tunnel/service/state.go
@@ -117,15 +117,16 @@ func (t *RuntimeState) ToStatus(onlyInitialized bool) dto.TunnelStatus {
uptime = tunStart.Milliseconds()
clean := dto.TunnelStatus{
- Active: t.state.Active,
- Duration: uptime,
- Identities: make([]*dto.Identity, 0),
- IpInfo: t.state.IpInfo,
- LogLevel: t.state.LogLevel,
- ServiceVersion: Version,
- TunIpv4: t.state.TunIpv4,
- TunIpv4Mask: t.state.TunIpv4Mask,
- AddDns: t.state.AddDns,
+ Active: t.state.Active,
+ Duration: uptime,
+ Identities: make([]*dto.Identity, 0),
+ IpInfo: t.state.IpInfo,
+ LogLevel: t.state.LogLevel,
+ ServiceVersion: Version,
+ TunIpv4: t.state.TunIpv4,
+ TunIpv4Mask: t.state.TunIpv4Mask,
+ AddDns: t.state.AddDns,
+ NotificationFrequency: t.state.NotificationFrequency,
}
i := 0
@@ -154,12 +155,15 @@ func (t *RuntimeState) ToMetrics() dto.TunnelStatus {
for _, id := range t.ids {
AddMetrics(id)
clean.Identities[i] = &dto.Identity{
- Name: id.Name,
- FingerPrint: id.FingerPrint,
- Metrics: id.Metrics,
- Active: id.Active,
- MfaEnabled: id.MfaEnabled,
- MfaNeeded: id.MfaNeeded,
+ Name: id.Name,
+ FingerPrint: id.FingerPrint,
+ Metrics: id.Metrics,
+ Active: id.Active,
+ MfaEnabled: id.MfaEnabled,
+ MfaNeeded: id.MfaNeeded,
+ MfaMinTimeout: id.MfaMinTimeout,
+ MfaMaxTimeout: id.MfaMaxTimeout,
+ MfaLastUpdatedTime: id.MfaLastUpdatedTime,
}
i++
}
@@ -321,6 +325,10 @@ func (t *RuntimeState) LoadConfig() {
log.Warnf("provided mask: [%d] is smaller than the minimum permitted: [%d] and will be changed", rts.state.TunIpv4Mask, constants.Ipv4MinMask)
rts.UpdateIpv4Mask(constants.Ipv4MinMask)
}
+
+ if t.state.NotificationFrequency < constants.MinimumFrequency {
+ rts.UpdateNotificationFrequency(constants.MinimumFrequency)
+ }
}
func (t *RuntimeState) scanForOrphanedIdentities(folder string) {
@@ -544,3 +552,18 @@ func (t *RuntimeState) UpdateMfa(fingerprint string, mfaEnabled bool, mfaNeeded
id.MfaNeeded = mfaNeeded
}
}
+
+func (t *RuntimeState) UpdateNotificationFrequency(notificationFreq int) error {
+
+ log.Infof("setting notification frequency : %d", notificationFreq)
+
+ if notificationFreq < constants.MinimumFrequency || notificationFreq > constants.MaximumFrequency {
+ return errors.New(fmt.Sprintf("Notification frequency should be between %d and %d minutes", constants.MinimumFrequency, constants.MaximumFrequency))
+ }
+
+ rts.state.NotificationFrequency = notificationFreq
+
+ rts.SaveState()
+
+ return nil
+}
diff --git a/service/ziti-tunnel/service/types.go b/service/ziti-tunnel/service/types.go
index 67ec1495..9a6e2b0a 100644
--- a/service/ziti-tunnel/service/types.go
+++ b/service/ziti-tunnel/service/types.go
@@ -9,3 +9,8 @@ type Id struct {
dto.Identity
CId *cziti.ZIdentity
}
+
+type WindowsEvents struct {
+ WinPowerEvent uint32
+ WinSessionEvent uint32
+}
diff --git a/service/ziti-tunnel/version.go b/service/ziti-tunnel/version.go
index 52a10724..a3919eb2 100644
--- a/service/ziti-tunnel/version.go
+++ b/service/ziti-tunnel/version.go
@@ -20,9 +20,9 @@
package main
const (
- Version = "1.9.9"
- Revision = "b6a4ad327bd0"
- Branch = "main"
+ Version = "1.10.0"
+ Revision = "efac1fb0a5f2"
+ Branch = "release-next"
BuildUser = ""
- BuildDate = "2021-06-24 10:44:17"
+ BuildDate = "2021-07-22 15:53:14"
)