SQL Agent configuration for Operators, Alerts and Proxies (WIP) (#621)

* Initial non-refactored SQL Agent alert classes (WIP)

* Move agent classes into subdirectories

* Refactor the agent config code a bit more

* Add more implementation for handlers

* Add more code to the create alert handler

* Clean up agent alert class

* Clean up alert methods a bit

* Initial Operator contracts

* Additonal SQL Agent config changes

* More Proxy config cleanup

* Cleanup AgentProxy class

* Additional cleanups

* Run SRGen

* Add security service to create credential objects
This commit is contained in:
Karl Burtram
2018-05-30 08:58:02 -07:00
committed by GitHub
parent f5efe18e1b
commit 4f214f43e9
49 changed files with 8152 additions and 257 deletions

View File

@@ -0,0 +1,23 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using Microsoft.SqlTools.ServiceLayer.Agent;
namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
{
/// <summary>
/// a class for storing various credential properties
/// </summary>
public class CredentialInfo
{
public string Identity { get; set; }
public int Id { get; }
public DateTime DateLastModified { get; }
public DateTime CreateDate { get; }
public string ProviderName { get; set; }
}
}

View File

@@ -0,0 +1,45 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
using Microsoft.SqlTools.ServiceLayer.Utility;
using Microsoft.SqlTools.Utility;
namespace Microsoft.SqlTools.ServiceLayer.Security.Contracts
{
/// <summary>
/// Create Credential parameters
/// </summary>
public class CreateCredentialParams : GeneralRequestDetails
{
public string OwnerUri { get; set; }
public CredentialInfo Credential { get; set; }
}
/// <summary>
/// Create Credential result
/// </summary>
public class CreateCredentialResult
{
public bool Succeeded { get; set; }
public string ErrorMessage { get; set; }
}
/// <summary>
/// Create Credential request type
/// </summary>
public class CreateCredentialRequest
{
/// <summary>
/// Request definition
/// </summary>
public static readonly
RequestType<CreateCredentialParams, CreateCredentialResult> Type =
RequestType<CreateCredentialParams, CreateCredentialResult>.Create("security/createcredential");
}
}

View File

@@ -0,0 +1,114 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Xml;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Diagnostics;
using Microsoft.SqlTools.ServiceLayer.Management;
using Microsoft.SqlTools.ServiceLayer.Admin;
namespace Microsoft.SqlTools.ServiceLayer.Security
{
internal class Credential : ManagementActionBase
{
// #region Trace support
// private const string componentName = "Credential";
// public string ComponentName
// {
// get
// {
// return componentName;
// }
// }
// #endregion
#region Constants
private const int MAX_SQL_SYS_NAME_LENGTH = 128; // max sql sys name length
private const string PASSWORD_MASK_STRING = "**********";
#endregion
#region Variables
private CredentialData credentialData = null;
#endregion
#region Constructors / Dispose
/// <summary>
/// required when loading from Object Explorer context
/// </summary>
/// <param name="context"></param>
public Credential(CDataContainer context)
{
this.DataContainer = context;
this.credentialData = new CredentialData(context);
this.credentialData.Initialize();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
base.Dispose( disposing );
if (disposing == true)
{
if (this.credentialData != null)
{
this.credentialData.Dispose();
}
}
}
#endregion
#region Overrides SqlManagementUserControl
/// <summary>
/// called on background thread by the framework to execute the action
/// </summary>
/// <param name="node"></param>
public void OnRunNow(object sender)
{
this.credentialData.SendDataToServer();
}
#endregion
/// <summary>
/// update logic layer based on content of user interface
/// </summary>
private void UpdateLogicLayer()
{
this.credentialData.CredentialName = "this.textBoxCredentialName.Text";
this.credentialData.CredentialIdentity = "this.textBoxIdentity.Text";
this.credentialData.SecurePassword = AdminService.BuildSecureStringFromPassword("password");
this.credentialData.SecurePasswordConfirm = AdminService.BuildSecureStringFromPassword("password");
if (this.ServerConnection.ServerVersion.Major >= 10)
{
// need to update only during create time
this.credentialData.IsEncryptionByProvider = true; //this.checkBoxUseProvider.Checked;
if (this.credentialData.IsEncryptionByProvider)
{
this.credentialData.ProviderName = "this.comboBoxProviderName.SelectedItem.ToString()";
}
else
{
this.credentialData.ProviderName = string.Empty;
}
}
}
}
}

View File

@@ -0,0 +1,256 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Security;
using System.Text;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Diagnostics;
using Microsoft.SqlServer.Management.Sdk.Sfc;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer.Admin;
namespace Microsoft.SqlTools.ServiceLayer.Security
{
internal class CredentialException : Exception
{
public CredentialException(string message)
: base(message)
{
}
}
internal class CredentialData : IDisposable
{
#region Properties
private string credentialName = string.Empty;
public string CredentialName
{
get { return credentialName; }
set { credentialName = value; }
}
private string credentialIdentity = string.Empty;
public string CredentialIdentity
{
get { return credentialIdentity; }
set { credentialIdentity = value; }
}
private SecureString securePassword;
public SecureString SecurePassword
{
get { return securePassword; }
set
{
securePassword = value;
PasswordWasChanged = true;
}
}
private SecureString securePasswordConfirm;
public SecureString SecurePasswordConfirm
{
get { return securePasswordConfirm; }
set
{
securePasswordConfirm = value;
PasswordWasChanged = true;
}
}
private bool isPropertiesMode = false;
public bool IsPropertiesMode
{
get
{
return isPropertiesMode;
}
}
private bool passwordWasChanged = false;
public bool PasswordWasChanged
{
get { return passwordWasChanged; }
set { passwordWasChanged = value; }
}
private bool isEncryptionByProvider = false;
public bool IsEncryptionByProvider
{
get { return isEncryptionByProvider; }
set { isEncryptionByProvider = value; }
}
private string providerName = string.Empty;
public string ProviderName
{
get { return providerName; }
set { providerName = value; }
}
#endregion
private const string ENUMERATOR_FIELD_IDENTITY = "Identity";
private const string ENUMERATOR_FIELD_PROVIDER_NAME = "ProviderName";
#region Constructor
private CDataContainer context = null;
private CDataContainer Context { get { return context; } set { context = value; } }
public CredentialData(CDataContainer ctxt)
{
this.Context = ctxt;
LoadDataFromXmlContext();
}
#endregion
#region Implementation: LoadDataFromXmlContext(), LoadDataFromServer(), SendDataToServer()
public void Initialize()
{
LoadDataFromXmlContext();
LoadDataFromServer();
}
/// <summary>
/// LoadDataFromXmlContext
///
/// loads context information from xml - e.g. name of object
/// </summary>
private void LoadDataFromXmlContext()
{
this.CredentialName = this.Context.GetDocumentPropertyString("credential");
this.isPropertiesMode = (this.CredentialName != null) && (this.CredentialName.Length > 0);
}
/// <summary>
/// LoadDataFromServer
///
/// talks with enumerator an retrieves info that is not available in the xml context but on server
/// </summary>
private void LoadDataFromServer()
{
if (this.IsPropertiesMode == true)
{
bool isKatmaiAndNotMatrix = (this.context.Server.Version.Major >= 10);
Urn urn = new Urn("Server/Credential[@Name='" + Urn.EscapeString(this.CredentialName) + "']");
string [] fields;
if (isKatmaiAndNotMatrix)
{
fields = new string[] { ENUMERATOR_FIELD_IDENTITY, ENUMERATOR_FIELD_PROVIDER_NAME };
}
else
{
fields = new string[] { ENUMERATOR_FIELD_IDENTITY };
}
Request r = new Request(urn, fields);
System.Data.DataTable dataTable = Enumerator.GetData(this.Context.ConnectionInfo, r);
if (dataTable.Rows.Count == 0)
{
throw new Exception("SRError.CredentialNoLongerExists");
}
System.Data.DataRow dataRow = dataTable.Rows[0];
this.CredentialIdentity = Convert.ToString(dataRow[ENUMERATOR_FIELD_IDENTITY], System.Globalization.CultureInfo.InvariantCulture);
if (isKatmaiAndNotMatrix)
{
this.providerName = Convert.ToString(dataRow[ENUMERATOR_FIELD_PROVIDER_NAME], System.Globalization.CultureInfo.InvariantCulture);
this.isEncryptionByProvider = !string.IsNullOrEmpty(providerName);
}
}
else
{
this.CredentialName = string.Empty;
this.CredentialIdentity = string.Empty;
this.providerName = string.Empty;
this.isEncryptionByProvider = false;
}
this.SecurePassword = new SecureString();
this.SecurePasswordConfirm = new SecureString();
this.PasswordWasChanged = false;
}
/// <summary>
/// SendDataToServer
///
/// here we talk with server via smo and do the actual data changing
/// </summary>
public void SendDataToServer()
{
if (this.IsPropertiesMode == true)
{
SendToServerAlterCredential();
}
else
{
SendToServerCreateCredential();
}
}
/// <summary>
/// create credential - create mode
/// </summary>
private void SendToServerCreateCredential()
{
Microsoft.SqlServer.Management.Smo.Credential smoCredential = new Microsoft.SqlServer.Management.Smo.Credential (
this.Context.Server,
this.CredentialName);
if (this.isEncryptionByProvider)
{
smoCredential.MappedClassType = MappedClassType.CryptographicProvider;
smoCredential.ProviderName = this.providerName;
}
smoCredential.Create(this.CredentialIdentity, this.SecurePassword.ToString());
GC.Collect(); // this.SecurePassword.ToString() just created an immutable string that lives in memory
}
/// <summary>
/// alter credential - properties mode
/// </summary>
private void SendToServerAlterCredential()
{
Microsoft.SqlServer.Management.Smo.Credential smoCredential = this.Context.Server.Credentials[this.CredentialName];
if (smoCredential != null)
{
if (this.PasswordWasChanged == false)
{
if (smoCredential.Identity != this.CredentialIdentity)
{
smoCredential.Alter(this.CredentialIdentity);
}
}
else
{
smoCredential.Alter(this.CredentialIdentity, this.SecurePassword.ToString());
GC.Collect(); // this.SecurePassword.ToString() just created an immutable string that lives in memory
}
}
else
{
throw new Exception("SRError.CredentialNoLongerExists");
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
this.SecurePassword.Dispose();
this.SecurePasswordConfirm.Dispose();
}
#endregion
}
}

View File

@@ -0,0 +1,116 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Admin;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.Security.Contracts;
namespace Microsoft.SqlTools.ServiceLayer.Security
{
/// <summary>
/// Main class for Security Service functionality
/// </summary>
public sealed class SecurityService : IDisposable
{
private bool disposed;
private ConnectionService connectionService = null;
private static readonly Lazy<SecurityService> instance = new Lazy<SecurityService>(() => new SecurityService());
/// <summary>
/// Construct a new SecurityService instance with default parameters
/// </summary>
public SecurityService()
{
}
/// <summary>
/// Gets the singleton instance object
/// </summary>
public static SecurityService Instance
{
get { return instance.Value; }
}
/// <summary>
/// Internal for testing purposes only
/// </summary>
internal ConnectionService ConnectionServiceInstance
{
get
{
if (connectionService == null)
{
connectionService = ConnectionService.Instance;
}
return connectionService;
}
set
{
connectionService = value;
}
}
/// <summary>
/// Service host object for sending/receiving requests/events.
/// Internal for testing purposes.
/// </summary>
internal IProtocolEndpoint ServiceHost
{
get;
set;
}
/// <summary>
/// Initializes the Security Service instance
/// </summary>
public void InitializeService(ServiceHost serviceHost)
{
this.ServiceHost = serviceHost;
this.ServiceHost.SetRequestHandler(CreateCredentialRequest.Type, HandleCreateCredentialRequest);
}
/// <summary>
/// Handle request to create a credential
/// </summary>
internal async Task HandleCreateCredentialRequest(CreateCredentialParams parameters, RequestContext<CreateCredentialResult> requestContext)
{
try
{
var result = new CreateCredentialResult();
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(
parameters.OwnerUri,
out connInfo);
CDataContainer dataContainer = AdminService.CreateDataContainer(connInfo, databaseExists: true);
Credential credential = new Credential(dataContainer);
await requestContext.SendResult(result);
}
catch (Exception e)
{
await requestContext.SendError(e);
}
}
/// <summary>
/// Disposes the service
/// </summary>
public void Dispose()
{
if (!disposed)
{
disposed = true;
}
}
}
}