From 0a4ff82af0f98ecf4ead70ce43a85bb91fec3d7d Mon Sep 17 00:00:00 2001 From: Trevor Payne Date: Thu, 15 Apr 2021 13:47:46 -0500 Subject: [PATCH] Added SecretsManager Added ISecrets Added ISecretsProvider --- PepperDashEssentials/ControlSystem.cs | 1 + .../Comm and IR/GenericComm.cs | 1 + .../Config/DeviceConfig.cs | 14 +- .../Factory/DeviceFactory.cs | 98 ++++++-- .../PepperDash_Essentials_Core.csproj | 4 + .../Secrets/CrestronSecretsProvider.cs | 84 +++++++ .../Secrets/Interfaces.cs | 18 ++ .../Secrets/SecretsManager.cs | 220 ++++++++++++++++++ .../Secrets/SecretsPropertiesConfig.cs | 17 ++ 9 files changed, 438 insertions(+), 19 deletions(-) create mode 100644 essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecretsProvider.cs create mode 100644 essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/Interfaces.cs create mode 100644 essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsManager.cs create mode 100644 essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsPropertiesConfig.cs diff --git a/PepperDashEssentials/ControlSystem.cs b/PepperDashEssentials/ControlSystem.cs index 5df5c1a2..37ef79a8 100644 --- a/PepperDashEssentials/ControlSystem.cs +++ b/PepperDashEssentials/ControlSystem.cs @@ -36,6 +36,7 @@ namespace PepperDash.Essentials Thread.MaxNumberOfUserThreads = 400; Global.ControlSystem = this; DeviceManager.Initialize(this); + SecretsManager.Initialize(); SystemMonitor.ProgramInitialization.ProgramInitializationUnderUserControl = true; } diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/GenericComm.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/GenericComm.cs index 8380a290..7648a379 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/GenericComm.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Comm and IR/GenericComm.cs @@ -25,6 +25,7 @@ namespace PepperDash.Essentials.Core public GenericComm(DeviceConfig config) : base(config) { + PropertiesConfig = CommFactory.GetControlPropertiesConfig(config); var commPort = CommFactory.CreateCommForDevice(config); diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs index bb95da01..1d9ed1c2 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Config/DeviceConfig.cs @@ -30,7 +30,19 @@ namespace PepperDash.Essentials.Core.Config [JsonProperty("properties")] [JsonConverter(typeof(DevicePropertiesConverter))] - public JToken Properties { get; set; } + public JToken Properties { get; set; } + + public DeviceConfig(DeviceConfig dc) + { + Key = dc.Key; + Uid = dc.Uid; + Name = dc.Name; + Group = dc.Group; + Type = dc.Type; + Properties = JToken.FromObject(dc.Properties); + } + + public DeviceConfig() {} } /// diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/DeviceFactory.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/DeviceFactory.cs index 3f38e2d5..a0108490 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/DeviceFactory.cs +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Factory/DeviceFactory.cs @@ -7,6 +7,8 @@ using Crestron.SimplSharpPro; using Crestron.SimplSharpPro.GeneralIO; using Crestron.SimplSharp.Reflection; using PepperDash.Core; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; using PepperDash.Essentials.Core; using PepperDash.Essentials.Core.Config; using PepperDash.Essentials.Core.CrestronIO; @@ -91,24 +93,84 @@ namespace PepperDash.Essentials.Core /// /// /// - public static IKeyed GetDevice(DeviceConfig dc) - { - var key = dc.Key; - var name = dc.Name; - var type = dc.Type; - var properties = dc.Properties; - - var typeName = dc.Type.ToLower(); - - // Check for types that have been added by plugin dlls. - if (FactoryMethods.ContainsKey(typeName)) - { - Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loading '{0}' from Essentials Core", dc.Type); - return FactoryMethods[typeName].FactoryMethod(dc); - } - - return null; - } + public static IKeyed GetDevice(DeviceConfig dc) + { + try + { + Debug.Console(0, Debug.ErrorLogLevel.Notice, "Loading '{0}' from Essentials Core", dc.Type); + + var localDc = new DeviceConfig(dc); + + var key = localDc.Key; + var name = localDc.Name; + var type = localDc.Type; + var properties = localDc.Properties; + + + + var typeName = localDc.Type.ToLower(); + + Debug.Console(2, "typeName = {0}", typeName); + // Check for types that have been added by plugin dlls. + if (FactoryMethods.ContainsKey(typeName)) + { + //look for secret in username + var userSecretToken = properties["control"]["tcpSshProperties"]["username"]["secret"]; + + if (userSecretToken != null) + { + Debug.Console(2, "Found a secret for {0} - attempting to retrieve it!", name); + var userSecretResult = + JsonConvert.DeserializeObject(userSecretToken.ToString()); + var userProvider = SecretsManager.GetSecretProviderByKey(userSecretResult.Provider); + if (userProvider != null) + { + var user = userProvider.GetSecret(userSecretResult.Key); + if (user == null) + { + Debug.Console(1, + "Unable to retrieve secret for {0} - Make sure you've added it to the secrets provider"); + return null; + } + properties["control"]["tcpSshProperties"]["username"] = (string) user.Value; + } + } + + //look for secret in password + var passwordSecretToken = properties["control"]["tcpSshProperties"]["password"]["secret"]; + + if (passwordSecretToken != null) + { + Debug.Console(2, "Found a secret for {0} - attempting to retrieve it!", name); + + var passwordSecretResult = + JsonConvert.DeserializeObject(passwordSecretToken.ToString()); + var passwordProvider = SecretsManager.GetSecretProviderByKey(passwordSecretResult.Provider); + if (passwordProvider != null) + { + var password = passwordProvider.GetSecret(passwordSecretResult.Key); + if (password == null) + { + Debug.Console(1, + "Unable to retrieve secret for {0} - Make sure you've added it to the secrets provider"); + return null; + } + properties["control"]["tcpSshProperties"]["password"] = (string) password.Value; + } + } + + Debug.Console(0, "{0}", localDc.Properties.ToString()); + + return FactoryMethods[typeName].FactoryMethod(localDc); + } + return null; + } + catch (Exception ex) + { + Debug.Console(2, "Issue with getting device - {0}", ex.Message); + return null; + } + } /// /// Prints the type names and associated metadata from the FactoryMethods collection. diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj index e4c775d0..f202434e 100644 --- a/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/PepperDash_Essentials_Core.csproj @@ -320,6 +320,10 @@ + + + + diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecretsProvider.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecretsProvider.cs new file mode 100644 index 00000000..54a3f3c8 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/CrestronSecretsProvider.cs @@ -0,0 +1,84 @@ +using System; +using Crestron.SimplSharp; +using Crestron.SimplSharp.CrestronDataStore; +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + public class CrestronSecretsProvider : ISecretProvider + { + public string Key { get; set; } + //Added for reference + //private readonly bool _secureSupported; + public CrestronSecretsProvider(string key) + { + Key = key; + //Added for future encrypted reference + //_secureSupported = CrestronSecureStorage.Supported; + + //if (_secureSupported) + //{ + // return; + //} + CrestronDataStoreStatic.InitCrestronDataStore(); + + } + + + public void SetSecret(string key, object value) + { + var secret = value as string; + if (String.IsNullOrEmpty(secret)) + { + Debug.Console(2, this, "Unable to set secret for {0}:{1} - value is empty.", Key, key); + return; + } + Debug.Console(2, this, "Attempting to set Secret to {0}", secret); + var setErrorCode = CrestronDataStoreStatic.SetLocalStringValue(key, secret); + switch (setErrorCode) + { + case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: + Debug.Console(2, this,"Secret Successfully Set for {0}:{1}", Key, key); + break; + default: + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Unable to set secret for {0}:{1} - {2}", Key, key, setErrorCode.ToString()); + break; + } + } + + public ISecret GetSecret(string key) + { + string mySecret; + var getErrorCode = CrestronDataStoreStatic.GetLocalStringValue(key, out mySecret); + + switch (getErrorCode) + { + case CrestronDataStore.CDS_ERROR.CDS_SUCCESS: + Debug.Console(2, this, "Secret Successfully retrieved for {0}:{1}", Key, key); + Debug.Console(2, this, "Retreived Secret = {0}", mySecret); + return new CrestronSecret(key, mySecret, this); + default: + Debug.Console(2, this, Debug.ErrorLogLevel.Notice, "Unable to retrieve secret for {0}:{1} - {2}", + Key, key, getErrorCode.ToString()); + return null; + } + } + } + + public class CrestronSecret : ISecret + { + public ISecretProvider Provider { get; private set; } + public string Key { get; private set; } + + public object Value { get; private set; } + + public CrestronSecret(string key, string value, ISecretProvider provider) + { + Key = key; + Value = value; + Provider = provider; + } + + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/Interfaces.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/Interfaces.cs new file mode 100644 index 00000000..51b0b389 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/Interfaces.cs @@ -0,0 +1,18 @@ +using PepperDash.Core; + +namespace PepperDash.Essentials.Core +{ + public interface ISecretProvider : IKeyed + { + void SetSecret(string key, object value); + + ISecret GetSecret(string key); + } + + public interface ISecret + { + ISecretProvider Provider { get; } + string Key { get; } + object Value { get; } + } +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsManager.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsManager.cs new file mode 100644 index 00000000..c14bfdc6 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsManager.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Crestron.SimplSharp; +using PepperDash.Core; + + +namespace PepperDash.Essentials.Core +{ + public static class SecretsManager + { + public static List Secrets { get; set; } + + public static void Initialize() + { + Secrets = new List {new CrestronSecretsProvider("default")}; + + CrestronConsole.AddNewConsoleCommand(SetSecretProcess, "setsecret", + "Adds secrets to secret provider", + ConsoleAccessLevelEnum.AccessOperator); + + CrestronConsole.AddNewConsoleCommand(UpdateSecretProcess, "updatesecret", + "Updates secrets in secret provider", + ConsoleAccessLevelEnum.AccessAdministrator); + + CrestronConsole.AddNewConsoleCommand(DeleteSecretProcess, "deletesecret", + "Deletes secrets in secret provider", + ConsoleAccessLevelEnum.AccessAdministrator); + + + } + + public static ISecretProvider GetSecretProviderByKey(string key) + { + var secret = Secrets.FirstOrDefault(o => o.Key == key); + if (secret == null) + { + Debug.Console(1, "SecretsManager unable to retrieve SecretProvider with the key '{0}'", key); + } + return secret; + } + + private static void SetSecretProcess(string cmd) + { + string response; + var args = cmd.Split(' '); + + if (args.Length == 0) + { + //some Instructional Text + response = "Adds secrets to secret provider. Format 'setsecret "; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + if (args.Length == 1 && args[0] == "?") + { + response = "Adds secrets to secret provider. Format 'setsecret "; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + if (args.Length < 3) + { + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var provider = Secrets.FirstOrDefault(o => o.Key == args[0]); + + if (provider == null) + { + //someFail + response = "Provider key invalid"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var key = args[1]; + var secret = args[2]; + + if (provider.GetSecret(key) == null) + { + provider.SetSecret(key, secret); + response = + String.Format( + "Secret successfully set for {0}:{1}", + provider.Key, key); + CrestronConsole.ConsoleCommandResponse(response); + return; + } + response = + String.Format( + "Unable to set secret for {0}:{1} - Please use the 'UpdateSecret' command to modify it"); + CrestronConsole.ConsoleCommandResponse(response); + } + + private static void UpdateSecretProcess(string cmd) + { + string response; + var args = cmd.Split(' '); + + if (args.Length == 0) + { + //some Instructional Text + response = "Updates secrets in secret provider. Format 'updatesecret "; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + if (args.Length == 1 && args[0] == "?") + { + response = "Updates secrets in secret provider. Format 'updatesecret "; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + + if (args.Length < 3) + { + //someFail + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var provider = Secrets.FirstOrDefault(o => o.Key == args[0]); + + if (provider == null) + { + //someFail + response = "Provider key invalid"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var key = args[1]; + var secret = args[2]; + + if (provider.GetSecret(key) != null) + { + provider.SetSecret(key, secret); + response = + String.Format( + "Secret successfully updated for {0}:{1}", + provider.Key, key); + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + response = + String.Format( + "Unable to update secret for {0}:{1} - Please use the 'SetSecret' command to create a new secret"); + CrestronConsole.ConsoleCommandResponse(response); + } + + private static void DeleteSecretProcess(string cmd) + { + string response; + var args = cmd.Split(' '); + + if (args.Length == 0) + { + //some Instructional Text + response = "Deletes secrets in secret provider. Format 'deletesecret "; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + if (args.Length == 1 && args[0] == "?") + { + response = "Deletes secrets in secret provider. Format 'deletesecret "; + CrestronConsole.ConsoleCommandResponse(response); + return; + } + + + + if (args.Length < 2) + { + //someFail + response = "Improper number of arguments"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var provider = Secrets.FirstOrDefault(o => o.Key == args[0]); + + if (provider == null) + { + //someFail + response = "Provider key invalid"; + CrestronConsole.ConsoleCommandResponse(response); + return; + + } + + var key = args[1]; + + + provider.SetSecret(key, ""); + response = + String.Format( + "Secret successfully deleted for {0}:{1}", + provider.Key, key); + CrestronConsole.ConsoleCommandResponse(response); + + + } + } + + +} \ No newline at end of file diff --git a/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsPropertiesConfig.cs b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsPropertiesConfig.cs new file mode 100644 index 00000000..b17b9000 --- /dev/null +++ b/essentials-framework/Essentials Core/PepperDashEssentialsBase/Secrets/SecretsPropertiesConfig.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Crestron.SimplSharp; +using Newtonsoft.Json; + +namespace PepperDash.Essentials.Core +{ + public class SecretsPropertiesConfig + { + [JsonProperty("provider")] + public string Provider { get; set; } + [JsonProperty("key")] + public string Key { get; set; } + } +} \ No newline at end of file