Skip to content

Commit

Permalink
Add configuration to record sanitized sql query for .NET Core (#49)
Browse files Browse the repository at this point in the history
* Add command text to SQL command

* Add configuration to allow collection of query text

Converts XRayOptions fields to auto-properties
Adds InternalsVisibleTo so tests can access internal members

* PR feedback

* added CollectSqlQueries to configuration

* Removed doc references to TraceableSqlCommand to avoid circular referencing

* Added tests to testXRayOptions to test for the CollectSqlQueries configuration parameter.

* added missing json files for unit tests
  • Loading branch information
jaredcnance authored and Yogiraj Awati committed Jul 2, 2019
1 parent 3f60868 commit e9a1ff4
Show file tree
Hide file tree
Showing 11 changed files with 599 additions and 94 deletions.
67 changes: 39 additions & 28 deletions sdk/src/Core/Internal/Utils/AppSettings.netcore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ namespace Amazon.XRay.Recorder.Core.Internal.Utils
/// </summary>
public class XRayOptions
{
private string _pluginSetting;
private string _samplingRuleManifest;
private string _awsServiceHandlerManifest;
private bool _isXRayTracingDisabled;
private bool _useRuntimeErrors = true;

/// <summary>
/// Default constructor.
/// </summary>
Expand All @@ -43,49 +37,66 @@ public XRayOptions()
/// <param name="isXRayTracingDisabled">Tracing disabled value, either true or false.</param>
public XRayOptions(string pluginSetting, string samplingRuleManifest, string awsServiceHandlerManifest,
bool isXRayTracingDisabled) : this(pluginSetting, samplingRuleManifest, awsServiceHandlerManifest, isXRayTracingDisabled, true)
{
}

/// <summary>
/// Creates instance of <see cref="XRayOptions"/>
/// </summary>
/// <param name="pluginSetting">Plugin setting.</param>
/// <param name="samplingRuleManifest">Sampling rule file path</param>
/// <param name="awsServiceHandlerManifest">AWS Service manifest file path.</param>
{
}

/// <summary>
/// Creates instance of <see cref="XRayOptions"/>
/// </summary>
/// <param name="pluginSetting">Plugin setting.</param>
/// <param name="samplingRuleManifest">Sampling rule file path</param>
/// <param name="awsServiceHandlerManifest">AWS Service manifest file path.</param>
/// <param name="isXRayTracingDisabled">Tracing disabled value, either true or false.</param>
/// <param name="useRuntimeErrors">Should errors be thrown at runtime if segment not started, either true or false.</param>
public XRayOptions(string pluginSetting, string samplingRuleManifest, string awsServiceHandlerManifest, bool isXRayTracingDisabled, bool useRuntimeErrors)
/// <param name="collectSqlQueries">
/// Include the TraceableSqlCommand.CommandText in the sanitized_query section of
/// the SQL subsegment. Parameterized values will appear in their tokenized form and will not be expanded.
/// You should not enable this flag if you are including sensitive information as clear text.
/// This flag can also be overridden for each TraceableSqlCommand instance individually.
/// See the official documentation on <a href="https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.parameters?view=netframework-4.7.2">SqlCommand.Parameters</a>
/// </param>
public XRayOptions(string pluginSetting, string samplingRuleManifest, string awsServiceHandlerManifest, bool isXRayTracingDisabled, bool useRuntimeErrors, bool collectSqlQueries = false)
{
PluginSetting = pluginSetting;
SamplingRuleManifest = samplingRuleManifest;
AwsServiceHandlerManifest = awsServiceHandlerManifest;
IsXRayTracingDisabled = isXRayTracingDisabled;
UseRuntimeErrors = useRuntimeErrors;
CollectSqlQueries = collectSqlQueries;
}

/// <summary>
/// Plugin setting.
/// <summary>
/// Plugin setting.
/// </summary>
public string PluginSetting { get => _pluginSetting; set => _pluginSetting = value; }
public string PluginSetting { get; set; }

/// <summary>
/// Sampling rule file path.
/// <summary>
/// Sampling rule file path.
/// </summary>
public string SamplingRuleManifest { get => _samplingRuleManifest; set => _samplingRuleManifest = value; }
public string SamplingRuleManifest { get; set; }

/// <summary>
/// AWS Service manifest file path.
/// <summary>
/// AWS Service manifest file path.
/// </summary>
public string AwsServiceHandlerManifest { get => _awsServiceHandlerManifest; set => _awsServiceHandlerManifest = value; }

public string AwsServiceHandlerManifest { get; set; }

/// <summary>
/// Tracing disabled value, either true or false.
/// </summary>
public bool IsXRayTracingDisabled { get => _isXRayTracingDisabled; set => _isXRayTracingDisabled = value; }
public bool IsXRayTracingDisabled { get; set; }

/// <summary>
/// For missing Segments/Subsegments, if set to true, runtime exception is thrown, if set to false, runtime exceptions are avoided and logged.
/// </summary>
public bool UseRuntimeErrors { get => _useRuntimeErrors; set => _useRuntimeErrors = value; }
public bool UseRuntimeErrors { get; set; } = true;

/// <summary>
/// Include the TraceableSqlCommand.CommandText in the sanitized_query section of
/// the SQL subsegment. Parameterized values will appear in their tokenized form and will not be expanded.
/// You should not enable this flag if you are not including sensitive information as clear text.
/// When set to true, the sanitized sql query will be recorded for all the instances of TraceableSqlCommand
/// in the application, unless it is overridden on the individual TraceableSqlCommand instances.
/// </summary>
public bool CollectSqlQueries { get; set; } = false;
}
}
2 changes: 2 additions & 0 deletions sdk/src/Core/Internal/Utils/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public static class XRayConfiguration
private const string AWSServiceHandlerManifestKey = "AWSServiceHandlerManifest";
private const string DisableXRayTracingKey = "DisableXRayTracing";
private const string UseRuntimeErrorsKey = "UseRuntimeErrors";
private const string CollectSqlQueries = "CollectSqlQueries";

/// <summary>
/// Reads configuration from <see cref="IConfiguration"/> object for X-Ray.
Expand Down Expand Up @@ -63,6 +64,7 @@ private static XRayOptions GetXRayOptions(IConfiguration config, string configSe
options.AwsServiceHandlerManifest =GetSetting(AWSServiceHandlerManifestKey, section);
options.IsXRayTracingDisabled = GetSettingBool(DisableXRayTracingKey,section);
options.UseRuntimeErrors = GetSettingBool(UseRuntimeErrorsKey, section, defaultValue: true);
options.CollectSqlQueries = GetSettingBool(CollectSqlQueries, section, defaultValue: false);
return options;
}

Expand Down
10 changes: 9 additions & 1 deletion sdk/src/Core/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
using System;
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

[assembly: ComVisible(false)]
[assembly: CLSCompliant(true)]
[assembly: InternalsVisibleTo("AWSXRayRecorder.UnitTests,PublicKey="+
"0024000004800000940000000602000000240000525341310004000001000100712913451f6deb"
+ "158da1d2129b21119cca7d4eebeef5b310e8acd7f2d9506346071207652f1210a3bfa1545d6897"
+ "a607fc3a515954e660ec6fc5797730022867514e58411e8ecd61c767a319d2c29facee20f5d4f4"
+ "2b5425f27518616a8f4c1e5ac0e3e2b407bd8786d1b360af6b49c2b987478fe76b124c72f48864"
+ "55199df6"
)]
155 changes: 155 additions & 0 deletions sdk/src/Handlers/SqlServer/DbCommandInterceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//-----------------------------------------------------------------------------
// <copyright file="DbCommandInterceptor.cs" company="Amazon.com">
// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License").
// You may not use this file except in compliance with the License.
// A copy of the License is located at
//
// http://aws.amazon.com/apache2.0
//
// or in the "license" file accompanying this file. This file 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.
// </copyright>
//-----------------------------------------------------------------------------

using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Amazon.XRay.Recorder.Core;

namespace Amazon.XRay.Recorder.Handlers.SqlServer
{
/// <summary>
/// Intercepts DbCommands and records them in new Subsegments.
/// </summary>
public interface IDbCommandInterceptor
{
/// <summary>
/// Begins a new Subsegment, executes the provided async operation,
/// and records the request in the "sql" member of the subsegment.
/// </summary>
/// <example>
/// <code>
/// await InterceptAsync(() => dbCommand.ExecuteNonQueryAsync(cancellationToken), dbCommand);
/// </code>
/// </example>
Task<TResult> InterceptAsync<TResult>(Func<Task<TResult>> method, DbCommand command);

/// <summary>
/// Begins a new Subsegment, executes the provided operation,
/// and records the request in the "sql" member of the subsegment.
/// </summary>
/// <example>
/// <code>
/// await Intercept(() => dbCommand.ExecuteNonQuery(), dbCommand);
/// </code>
/// </example>
TResult Intercept<TResult>(Func<TResult> method, DbCommand command);
}

/// <inheritdoc />
public class DbCommandInterceptor : IDbCommandInterceptor
{
private const string DataBaseTypeString = "sqlserver";
private readonly AWSXRayRecorder _recorder;
private readonly bool? _collectSqlQueriesOverride;

public DbCommandInterceptor(AWSXRayRecorder recorder, bool? collectSqlQueries = null)
{
_recorder = recorder;
_collectSqlQueriesOverride = collectSqlQueries;
}

/// <inheritdoc />
public async Task<TResult> InterceptAsync<TResult>(Func<Task<TResult>> method, DbCommand command)
{
_recorder.BeginSubsegment(BuildSubsegmentName(command));
try
{
_recorder.SetNamespace("remote");
var ret = await method();
CollectSqlInformation(command);

return ret;
}
catch (Exception e)
{
_recorder.AddException(e);
throw;
}
finally
{
_recorder.EndSubsegment();
}
}

/// <inheritdoc />
public TResult Intercept<TResult>(Func<TResult> method, DbCommand command)
{
_recorder.BeginSubsegment(BuildSubsegmentName(command));
try
{
_recorder.SetNamespace("remote");
var ret = method();
CollectSqlInformation(command);

return ret;
}
catch (Exception e)
{
_recorder.AddException(e);
throw;
}
finally
{
_recorder.EndSubsegment();
}
}

/// <summary>
/// Records the SQL information on the current subsegment,
/// </summary>
protected virtual void CollectSqlInformation(DbCommand command)
{
_recorder.AddSqlInformation("database_type", DataBaseTypeString);

_recorder.AddSqlInformation("database_version", command.Connection.ServerVersion);

SqlConnectionStringBuilder connectionStringBuilder = new SqlConnectionStringBuilder(command.Connection.ConnectionString);

// Remove sensitive information from connection string
connectionStringBuilder.Remove("Password");

_recorder.AddSqlInformation("user", connectionStringBuilder.UserID);
_recorder.AddSqlInformation("connection_string", connectionStringBuilder.ToString());

if(ShouldCollectSqlText())
{
_recorder.AddSqlInformation("sanitized_query", command.CommandText);
}
}

/// <summary>
/// Builds the name of the subsegment in the format database@datasource
/// </summary>
/// <param name="command"></param>
/// <returns>Returns the formed subsegment name as a string.</returns>
private string BuildSubsegmentName(DbCommand command)
=> command.Connection.Database + "@" + SqlUtil.RemovePortNumberFromDataSource(command.Connection.DataSource);

#if !NET45
private bool ShouldCollectSqlText()
=> _collectSqlQueriesOverride ?? _recorder.XRayOptions.CollectSqlQueries;
#else
private bool ShouldCollectSqlText()
=> _collectSqlQueriesOverride ?? false;
#endif
}
}
Loading

0 comments on commit e9a1ff4

Please sign in to comment.