Mercurial > servermonitor
view ServerMonitor/Objects/Server.cs @ 37:547181b2b418
Bump version
author | Brad Greco <brad@bgreco.net> |
---|---|
date | Sun, 15 Sep 2019 20:51:13 -0400 |
parents | b5502ce8cb1f |
children | 7645122aa7a9 |
line wrap: on
line source
using System; using System.Linq; using System.Text; using System.Security.Cryptography; using System.ComponentModel; using Renci.SshNet; using System.Xml.Serialization; namespace ServerMonitorApp { /// <summary>Types of SSH logins supported by the server monitor.</summary> public enum LoginType { PrivateKey = 0, Password = 1 }; /// <summary>Remote server that checks can be run against.</summary> public class Server { private string _host; private int _port; private string _username; private LoginType _loginType; private string _keyFile; private SshClient _sshClient; private bool _enabled = true; private byte[] passwordHash; private PrivateKeyFile _privateKeyFile; /// <summary>Fires when a check belonging to this server is modifed.</summary> public event EventHandler CheckModified; /// <summary>Fires when the server enabled state changes.</summary> public event EventHandler EnabledChanged; /// <summary>The checks that belong to this server.</summary> public readonly BindingList<Check> Checks = new BindingList<Check>(); /// <summary>Internal ID of the server.</summary> public int Id { get; set; } /// <summary>Name of the server.</summary> public string Name { get; set; } /// <summary>Hostname of the server.</summary> public string Host { get { return _host; } set { _host = value; InvalidateSshConnection(); } } /// <summary>Port to use when connecting using SSH.</summary> public int Port { get { return _port; } set { _port = value; InvalidateSshConnection(); } } /// <summary>Username to use when connecting using SSH.</summary> public string Username { get { return _username; } set { _username = value; InvalidateSshConnection(); } } /// <summary>Login type to use when connecting using SSH.</summary> public LoginType LoginType { get { return _loginType; } set { _loginType = value; InvalidateSshConnection(); } } /// <summary>Path to the private key file to use when connecting using SSH.</summary> public string KeyFile { get { return _keyFile; } set { _keyFile = value; InvalidateSshConnection(); } } /// <summary>Password to use when connecting using SSH.</summary> /// <remarks>The password is encrypted using the current Windows user account.</remarks> public string Password { get { return passwordHash == null ? null : Encoding.UTF8.GetString(ProtectedData.Unprotect(passwordHash , Encoding.UTF8.GetBytes("Server".Reverse().ToString()) // Super-secure obfuscation of additional entropy , DataProtectionScope.CurrentUser)); } set { passwordHash = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), Encoding.UTF8.GetBytes("Server".Reverse().ToString()), // Super-secure obfuscation of additional entropy DataProtectionScope.CurrentUser); } } /// <summary>Private key file to use when connecting using SSH.</summary> /// <remarks> /// If the private key file is encrypted, will be null until the user enters /// the decryption key. /// </remarks> [XmlIgnore] public PrivateKeyFile PrivateKeyFile { get { return _privateKeyFile; } set { _privateKeyFile = value; if (LoginType == LoginType.PrivateKey) { if (_privateKeyFile == null) { // The private key has not been opened yet. // Disable the server until the user enters the decryption key, // and set the KeyStatus to indicate why the server was disabled. KeyStatus = KeyStatus.Closed; if (HasSshChecks) Enabled = false; } else { // The private key is open and accessible. // Automatically re-enable the server if it was previously disabled // due to a locked or inaccessible private key (i.e. disabled // programatically and not by user request). if (!KeyStatus.In(KeyStatus.Open, KeyStatus.Closed)) Enabled = true; KeyStatus = KeyStatus.Open; } } } } /// <summary>The current status of the private key file.</summary> public KeyStatus KeyStatus { get; set; } /// <summary>Whether this server's checks will be automatically executed on their schedules.</summary> public bool Enabled { get { return _enabled; } set { // Do not allow enabling the server if the private key is not accessible. // Do not fire the EnabledChanged event if the Enabled state is not actually changing // from its existing value. if ((LoginType == LoginType.PrivateKey && PrivateKeyFile == null && value == true && HasSshChecks) || value == _enabled) return; _enabled = value; EnabledChanged?.Invoke(this, new EventArgs()); } } /// <summary>The status of the server.</summary> /// <remarks> /// The status of the server is the most severe status of all its enabled checks. /// The integer value of the CheckStatus enum increases with the severity, /// so the maximum value of all checks gives the most severe status. /// </remarks> public CheckStatus Status => !Enabled ? CheckStatus.Disabled : Checks .Where(c => c.Enabled) .Select(c => c.LastRunStatus) .DefaultIfEmpty(CheckStatus.Success) .Max(); /// <summary>The SSH client to use when running checks on the server.</summary> /// <remarks> /// The connection is stored and kept open at the server level so it can be reused /// by all SSH checks. /// </remarks> public SshClient SshClient { get { if (_sshClient == null) { ConnectionInfo info = new ConnectionInfo(Host, Port, Username, GetAuthentication()); _sshClient = new SshClient(info); } return _sshClient; } } public bool HasSshChecks => Checks.Any(c => c is SshCheck); /// <summary>Deletes a check from the server.</summary> /// <param name="check">The check to delete.</param> public void DeleteCheck(Check check) { Checks.Remove(check); check.Server = null; CheckModified?.Invoke(check, new EventArgs()); } /// <summary>Validates server settings.</summary> /// <returns>An empty string if the server is valid, or the reason the server is invalid.</returns> public string Validate() { string message = string.Empty; if (Name.Length == 0) message += "\"Name\" must not be empty" + Environment.NewLine; if (Host.Length == 0) message += "\"Host\" must not be empty" + Environment.NewLine; return message.Length > 0 ? message : null; } /// <summary>Updates a check.</summary> public void UpdateCheck(Check check) { // See if there is already a check with this ID. Check oldCheck = Checks.FirstOrDefault(c => c.Id == check.Id); if (!ReferenceEquals(check, oldCheck)) { // If there is already a check, but it is a different object instance, // replace the old check with the new one (or add it if it is new). int index = Checks.IndexOf(oldCheck); if (index == -1) Checks.Add(check); else Checks[index] = check; } CheckModified?.Invoke(check, new EventArgs()); } /// <summary>Returns true if the server looks empty (no user data has been entered).</summary> public bool IsEmpty() { return Name.IsNullOrEmpty() && Host.IsNullOrEmpty() && Checks.Count == 0; } /// <summary>Generates the authentication method based on user preferences.</summary> private AuthenticationMethod GetAuthentication() { if (LoginType == LoginType.Password) return new PasswordAuthenticationMethod(Username, Password); else return new PrivateKeyAuthenticationMethod(Username, PrivateKeyFile); } /// <summary>Releases the open SSH connection.</summary> private void InvalidateSshConnection() { _sshClient?.Dispose(); _sshClient = null; } public override string ToString() { return Name.IsNullOrEmpty() ? Host : Name; } } /// <summary>Possible statuses of the private key file.</summary> public enum KeyStatus { /// <summary>The private key file is closed for an unspecified reason.</summary> Closed, /// <summary>The private key file is accessible and open.</summary> Open, /// <summary>The private key file is not accessible (missing, access denied, etc).</summary> NotAccessible, /// <summary>The private key file is encrypted and the user has not entered the password yet.</summary> NeedPassword, } }