From 08c2a31d1418d431caef9bfa7f4fd466f7d384b9 Mon Sep 17 00:00:00 2001 From: Artem Los Date: Thu, 25 Apr 2024 14:15:31 +0200 Subject: [PATCH] Load config string from command line (Windows service) (#11) * Update Helpers.cs * Update Program.cs * Update Program.cs * Load config from command line (non-Win service) * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update Program.cs * Update README.md * Update README.md * Update README.md * Update README.md * Update Program.cs --- LicenseServer/Helpers.cs | 21 ++++++++ LicenseServer/Program.cs | 109 +++++++++++++++++++++++++++++++++++---- README.md | 48 ++++++++++++++--- 3 files changed, 163 insertions(+), 15 deletions(-) diff --git a/LicenseServer/Helpers.cs b/LicenseServer/Helpers.cs index e33a189..a3e44d7 100644 --- a/LicenseServer/Helpers.cs +++ b/LicenseServer/Helpers.cs @@ -889,6 +889,27 @@ public static LicenseServerConfiguration ReadFromEnvironmentVariables(LicenseSer return lsc; } + + public static Dictionary ProcessCommandLineArgs(string[] args, Action updates) + { + var result = new Dictionary(); + + for (int i = 0; i < args.Length; i += 2) + { + if (args[i].StartsWith("-") && i + 1 < args.Length) + { + string key = args[i].Substring(1); + string value = args[i + 1]; + result[key] = value; + } + else + { + updates($"Warning: Ignored invalid or incomplete argument pair starting at index {i}"); + } + } + + return result; + } } public enum APIMethod diff --git a/LicenseServer/Program.cs b/LicenseServer/Program.cs index c83ab3f..820ff40 100644 --- a/LicenseServer/Program.cs +++ b/LicenseServer/Program.cs @@ -29,13 +29,14 @@ namespace LicenseServer { class Program { - public const string versionInfo = "v2.12-rc2 (2023-03-23)" ; + public const string versionInfo = "v2.13 (2024-04-25)" ; public const string ServiceName = "license-server"; public class Service : ServiceBase { - public Service() + public string[] args; + public Service(string[] args) { ServiceName = Program.ServiceName; @@ -44,13 +45,14 @@ public Service() System.Diagnostics.EventLog.CreateEventSource( "MySource", "MyNewLog"); } + this.args = args; } protected override void OnStart(string[] args) { //Program.Start(args); - Program.Initialization(args, runAsService: true); - EventLog.WriteEntry(""); + Program.Initialization(this.args, runAsService: true, x=> EventLog.WriteEntry(x), + x => EventLog.WriteEntry(x, System.Diagnostics.EventLogEntryType.Error)); } protected override void OnStop() @@ -83,8 +85,10 @@ static void Main(string[] args) { if (!Environment.UserInteractive) { - using (var service = new Service()) + using (var service = new Service(args)) + { ServiceBase.Run(service); + } } else { @@ -93,13 +97,16 @@ static void Main(string[] args) } - public static void Initialization(string[] args, bool runAsService = false) + public static void Initialization(string[] args, bool runAsService = false, Action writeMessage = null, Action writeError = null) { Console.WriteLine($"Cryptolens License Server {versionInfo}\n"); var envconfigstring = System.Environment.GetEnvironmentVariable("cryptolens_configurationstring"); - if (!string.IsNullOrEmpty(ConfigurationFromCryptolens) || runAsService || !string.IsNullOrEmpty(envconfigstring)) + var arguments = Helpers.ProcessCommandLineArgs(args, WriteMessage); + + if (!string.IsNullOrEmpty(ConfigurationFromCryptolens) || runAsService || !string.IsNullOrEmpty(envconfigstring) + || arguments != null && arguments.ContainsKey("config")) { LicenseServerConfiguration config = null; @@ -107,6 +114,16 @@ public static void Initialization(string[] args, bool runAsService = false) { config = Helpers.ReadConfiguration(envconfigstring, Constants.RSAPubKey); } + else if(string.IsNullOrEmpty(ConfigurationFromCryptolens) && args != null) + { + var parameters = Helpers.ProcessCommandLineArgs(args, WriteMessage); + + string configString = ""; + if(parameters!= null && parameters.TryGetValue("config", out configString)) + { + config = Helpers.ReadConfiguration(configString, Constants.RSAPubKey); + } + } else { config = Helpers.ReadConfiguration(ConfigurationFromCryptolens, Constants.RSAPubKey); @@ -116,6 +133,11 @@ public static void Initialization(string[] args, bool runAsService = false) { WriteMessage($"Configuration data could not be read. Attempting to read environment variables."); + if(runAsService) + { + writeError($"Configuration data could not be read. Attempting to read environment variables."); + } + config = Helpers.ReadFromEnvironmentVariables(null); } else @@ -123,6 +145,10 @@ public static void Initialization(string[] args, bool runAsService = false) if (config.PathToConfigFile == "USE_ENVIRONMENT_VARIABLES") { WriteMessage("Attempting to read environment variables."); + if (runAsService) + { + writeMessage("Attempting to read environment variables."); + } config = Helpers.ReadFromEnvironmentVariables(config); } } @@ -130,18 +156,45 @@ public static void Initialization(string[] args, bool runAsService = false) if (config.ValidUntil < DateTimeOffset.UtcNow) { WriteMessage($"Configuration data is outdated. Please contact the vendor to receive a new version of the license server."); + if (runAsService) + { + writeError($"Configuration data is outdated. Please contact the vendor to receive a new version of the license server."); + } return; } cacheLength = config.CacheLength; WriteMessage($"Cache length set to {cacheLength}"); + + if (runAsService) + { + writeMessage($"Cache length set to {cacheLength}"); + } + port = config.Port; WriteMessage($"Port set to {port}"); + + if (runAsService) + { + writeMessage($"Port set to {port}"); + } + attemptToRefresh = !config.OfflineMode; WriteMessage($"Offline mode is set to {!attemptToRefresh}"); + + if (runAsService) + { + writeMessage($"Offline mode is set to {!attemptToRefresh}"); + } + localFloatingServer = config.LocalFloatingServer; WriteMessage($"Local floating license server is set to {localFloatingServer}"); + if (runAsService) + { + writeMessage($"Local floating license server is set to {localFloatingServer}"); + } + RSAServerKey = config.ServerKey; RSAPublicKey = config.RSAPublicKey; @@ -153,6 +206,10 @@ public static void Initialization(string[] args, bool runAsService = false) { string result = Helpers.LoadLicenseFromPath(licenseCache, keysToUpdate, file, WriteMessage) ? "Processed" : "Error"; WriteMessage($"Path '{file}' {result}"); + if (runAsService) + { + writeError($"Path '{file}' {result}"); + } } } @@ -168,6 +225,10 @@ public static void Initialization(string[] args, bool runAsService = false) { port = configData.Port; WriteMessage($"Port changed to {port}."); + if (runAsService) + { + writeMessage($"Port changed to {port}."); + } } if(configData.ActivationFiles != null) @@ -176,19 +237,25 @@ public static void Initialization(string[] args, bool runAsService = false) { string result = Helpers.LoadLicenseFromPath(licenseCache, keysToUpdate, file, WriteMessage) ? "Processed" : "Error"; WriteMessage($"Path '{file}' {result}"); + if (runAsService) + { + writeMessage($"Path '{file}' {result}"); + } } } } catch (Exception ex) { WriteMessage($"Config file {config.PathToConfigFile} could not be read. Detailed error message {ex.Message}"); + if (runAsService) + { + writeError($"Config file {config.PathToConfigFile} could not be read. Detailed error message {ex.Message}"); + } } } } else { - - try { var config = Newtonsoft.Json.JsonConvert.DeserializeObject(System.IO.File.ReadAllText((Path.Combine(Directory.GetCurrentDirectory(), "config.json")))); @@ -324,24 +391,48 @@ public static void Initialization(string[] args, bool runAsService = false) httpListener.Prefixes.Add($"http://+:{port}/"); httpListener.Start(); WriteMessage("Starting server..."); + if (runAsService) + { + writeMessage("Starting server..."); + } + } catch (Exception ex) { WriteMessage($"Error: Please make sure that the license server runs as an administrator " + $"and that there is no other application that is listening to port {port}.\n\n" + $"Detailed error shown below: {ex.StackTrace.ToString()}"); + if (runAsService) + { + writeError($"Error: Please make sure that the license server runs as an administrator " + + $"and that there is no other application that is listening to port {port}.\n\n" + + $"Detailed error shown below: {ex.StackTrace.ToString()}"); + } Console.ReadLine(); return; } WriteMessage("Server started."); + if (runAsService) + { + writeMessage("Server started."); + } try { WriteMessage($"Server address is: {GetLocalIPAddress()}:{port}"); + if (runAsService) + { + writeMessage($"Server address is: {GetLocalIPAddress()}:{port}"); + } } catch (Exception ex) { WriteMessage("Could not get the IP of the license server."); + if (runAsService) + { + writeError("Could not get the IP of the license server."); + } + } if (cacheLength > 0) diff --git a/README.md b/README.md index e06e530..1ae3bea 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,25 @@ ![](example.png) ## Getting started -Since v2.12-rc, there are two ways you can configure the server. Either, you can use the [pre-built version](https://github.com/Cryptolens/license-server/releases) of the server or you can use the configuration string from [this page](https://app.cryptolens.io/extensions/LicenseServer?OfflineMode=False&LocalFloatingServer=False) and build the server on your end. +Since v2.12-rc, there are two ways you can configure the server. Either, you can use the [pre-built version](https://github.com/Cryptolens/license-server/releases) of the server or you can use the configuration string from [this page](https://app.cryptolens.io/extensions/LicenseServer?OfflineMode=False&LocalFloatingServer=False) and build the server yourself. ### Using the pre-built license server -You can use our [pre-built binaries](https://github.com/Cryptolens/license-server/releases) for all use cases (including the **local floating license server** feature or if you have the ability to set environment variables). +You can use our [pre-built binaries](https://github.com/Cryptolens/license-server/releases) for all use cases (including the **local floating license server** feature). -When you use the binaries, you can either store the configuration in the config file (as we describe later) or configure the server using environment variables (read more [here](#alternative-ways-to-configure-the-server)). Please keep in mind that the license server will **only read the environment variables** if you run it as a service, unless you set `cryptolens_configurationstring` environment variable with the configuration string from [this page](https://app.cryptolens.io/extensions/LicenseServer). +When you use the binaries, you can either store the configuration in the config file (as we describe later), configure the server using environment variables (read more [here](#alternative-ways-to-configure-the-server)) or pass on the configuration as a command line variable (read more [here](#providing-the-configuration-string-as-command-line-argument)). + +Please keep in mind that the license server will **only read the environment variables** or the **-config command line parameter** if you run it as a service (read more [here](#providing-the-configuration-string-as-command-line-argument)), unless you set `cryptolens_configurationstring` environment variable with the configuration string from [this page](https://app.cryptolens.io/extensions/LicenseServer). + +To sum up, you can start the server in two ways: +1. If you choose to use environment variable, you would need to set `cryptolens_configurationstring` to the configuration string. +2. If you instead want to supply the configuration string as a command line argument, you can start the server using as follows: +``` +LicenseSerer.exe -config +``` +3. By using a `config.json` file (not recommended). ### Building the server yourself -If you need the local floating license server and cannot use environment variables as described [here](#alternative-ways-to-configure-the-server), the license server needs to be compiled on your end to create the binaries. All configuration is stored inside the `ConfigurationFromCryptolens` variable in `Program.cs`, which can be created on [this page](https://app.cryptolens.io/extensions/LicenseServer?OfflineMode=False&LocalFloatingServer=False). In other words, there is no need to provide any arguments when calling the license server or use an external configuration file. +If you need the local floating license server and cannot use environment variables or pass the configuration string as a command line argument as described [here](#alternative-ways-to-configure-the-server), the license server needs to be compiled on your end to create the binaries. All configuration is stored inside the `ConfigurationFromCryptolens` variable in `Program.cs`, which can be created on [this page](https://app.cryptolens.io/extensions/LicenseServer?OfflineMode=False&LocalFloatingServer=False). In other words, there is no need to provide any arguments when calling the license server or use an external configuration file. The license server can be compiled on most operating systems and the process is as follows: @@ -123,7 +133,7 @@ If you want to use [floating licensing](https://help.cryptolens.io/licensing-mod 1. Visit [https://app.cryptolens.io/extensions/licenseserver](https://app.cryptolens.io/extensions/licenseserver) and copy the "License server configuration" and "RSA Public Key". 2. When verifying the signature inside your application, please use the RSA Public Key on this page instead of the one you would normally use when your application can access our API. This key will only work with the license server that uses the configuration above. -3. In the license server project, paste the value of "license server configuration" to `ConfigurationFromCryptolens` variable in `Program.cs`. +3. In the license server project, paste the value of "license server configuration" to `ConfigurationFromCryptolens` variable in `Program.cs`. In newer versions, you can choose to place the configuration string in an environment variable (`cryptolens_configurationstring`) or by supplying it as a command line argument (**-config**). 4. Compile the license server in release mode. 5. In the release folder, create a new folder called "licensefiles". 6. Visit the [product page](https://app.cryptolens.io/Product) and click on the yellow button next to the license key that belongs to your client (to manage all activations). Now, click on "Download activation file" and put this file into the "licensefiles" folder created earlier. @@ -192,7 +202,19 @@ sc create license-server binpath="D:\path\to\licenseserver\LicenseServer.exe" st net start license-server ``` -Note: the path to the license server needs to be absolute. Furthermore, it is important that the `ConfigurationFromCryptolens` variable is not empty and uses your own configuration. The configuration can be obtained on [https://app.cryptolens.io/extensions/licenseserver](https://app.cryptolens.io/extensions/licenseserver). When creating a new configuration, please set **Activation file folder** to an absolute path. For example, **C:\license-files**. +Note: the path to the license server needs to be absolute. It's important that you provide a configuration string that can be obtained at can be obtained on [https://app.cryptolens.io/extensions/licenseserver](https://app.cryptolens.io/extensions/licenseserver). When creating a new configuration, please set **Activation file folder** to an absolute path. For example, **C:\license-files**. + +Once you have the configuration string, you have three ways that you can start the server: + +#### If you have compiled the server yourself +1. Copy the configuration string that you obtained above into `ConfigurationFromCryptolens` variable and build the License Server yourself. + +#### If you are using the pre-built binaries +1. Create an environment variable `cryptolens_configurationstring` with the configuration string from above. +2. Supply the configuration string (from above) as a command line argument when registering LicenseServer.exe as a service, as follows: +``` +sc create license-server binpath="D:\path\to\licenseserver\LicenseServer.exe -config " start=auto +``` We have tested the license server version that targets .NET Framework 4.6.1. @@ -203,10 +225,14 @@ sc queryex license-server sc delete license-server ``` +> To obtain information about server status, e.g. the IP it is listening or if any errors have occurred, you can use the Windows Event log. + If you need any help, please let us know at support@cryptolens.io. ### Alternative ways to configure the server +#### Using environment variables + It is also possible to configure the license server using environment variables. For now, the license server will only read the environment variables in two cases: 1. If the `ConfigurationFromCryptolens` in Program.cs is not null or empty. @@ -227,3 +253,13 @@ Cryptolens uses the following environment variables: | `cryptolens_configurationstring` | An optional parameter that you can use to provide the configuration string that would normally need to be added in the code. The license server will always check for this environment variable at start and it will have priority over command line arguments.| > **Note**: when running the license server as a service, all paths to files and folders need to be **absolute**. + +#### Providing the configuration string as command line argument + +Since version v2.13, you can pass on the configuration string as a command line argument, meaning that you can start the server as follows: + +``` +dotnet LicenseServer.exe -config +``` + +The configuration string can be obtained at [https://app.cryptolens.io/extensions/licenseserver](https://app.cryptolens.io/extensions/licenseserver).