From 605c86e9fccc6883c1ebbcc3e7444395efc586a4 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Fri, 23 Jun 2023 13:32:04 +0200 Subject: [PATCH] Ensure Elastic.Extensions.Logging can set auth through config Previously this was only applied to Cloud connections --- ecs-dotnet.sln | 1 + .../Elastic.CommonSchema.Serilog.csproj | 8 ++-- .../Elastic.Extensions.Logging.csproj | 11 ++++-- .../ElasticsearchLoggerProvider.cs | 39 ++++++++++++++++--- .../Options/ShipToOptions.cs | 8 ++-- src/NullableExtensions.cs | 7 ++++ 6 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 src/NullableExtensions.cs diff --git a/ecs-dotnet.sln b/ecs-dotnet.sln index 4ebb0bac..57cc5ecd 100644 --- a/ecs-dotnet.sln +++ b/ecs-dotnet.sln @@ -28,6 +28,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7610B796-BB3E-4CB2-8296-79BBFF6D23FC}" ProjectSection(SolutionItems) = preProject src\Directory.Build.props = src\Directory.Build.props + src\NullableExtensions.cs = src\NullableExtensions.cs EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{3582B07D-C2B0-49CC-B676-EAF806EB010E}" diff --git a/src/Elastic.CommonSchema.Serilog/Elastic.CommonSchema.Serilog.csproj b/src/Elastic.CommonSchema.Serilog/Elastic.CommonSchema.Serilog.csproj index 8494303e..36c7de39 100644 --- a/src/Elastic.CommonSchema.Serilog/Elastic.CommonSchema.Serilog.csproj +++ b/src/Elastic.CommonSchema.Serilog/Elastic.CommonSchema.Serilog.csproj @@ -7,6 +7,7 @@ Serilog TextFormatter that formats log events in accordance with Elastic Common Schema (ECS). True enable + latest @@ -20,13 +21,12 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/src/Elastic.Extensions.Logging/Elastic.Extensions.Logging.csproj b/src/Elastic.Extensions.Logging/Elastic.Extensions.Logging.csproj index 5e0f54a4..d4feadce 100644 --- a/src/Elastic.Extensions.Logging/Elastic.Extensions.Logging.csproj +++ b/src/Elastic.Extensions.Logging/Elastic.Extensions.Logging.csproj @@ -5,18 +5,21 @@ Elasticsearch Logger Provider Elasticsearch logger provider for Microsoft.Extensions.Logging. Writes direct to Elasticsearch using the Elastic Common Schema (ECS), with semantic logging of structured data from message and scope values, for use with the Elasticsearch-Logstash-Kibana (ELK) stack. The results can be viewed and queried in the Kibana console. Logging;LoggerProvider;Elasticsearch;ELK;Kibana;Logstash;Tracing;Diagnostics;Log;Trace;ECS - 8 enable True - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/src/Elastic.Extensions.Logging/ElasticsearchLoggerProvider.cs b/src/Elastic.Extensions.Logging/ElasticsearchLoggerProvider.cs index ed2b2696..bb0acbbd 100644 --- a/src/Elastic.Extensions.Logging/ElasticsearchLoggerProvider.cs +++ b/src/Elastic.Extensions.Logging/ElasticsearchLoggerProvider.cs @@ -102,16 +102,26 @@ private static NodePool CreateNodePool(ElasticsearchLoggerOptions loggerOptions) return new StaticNodePool(nodeUris); // case NodePoolType.StickySniffing: case NodePoolType.Cloud: - if (!string.IsNullOrEmpty(shipTo.ApiKey)) + if (shipTo.CloudId.IsNullOrEmpty()) + throw new Exception($"Cloud {nameof(CloudNodePool)} requires '{nameof(ShipToOptions.CloudId)}' to be provided as well"); + + if (!shipTo.ApiKey.IsNullOrEmpty()) { var apiKeyCredentials = new ApiKey(shipTo.ApiKey); return new CloudNodePool(shipTo.CloudId, apiKeyCredentials); } - - var basicAuthCredentials = new BasicAuthentication(shipTo.Username, shipTo.Password); - return new CloudNodePool(shipTo.CloudId, basicAuthCredentials); + if (!shipTo.Username.IsNullOrEmpty() && !shipTo.Password.IsNullOrEmpty()) + { + var basicAuthCredentials = new BasicAuthentication(shipTo.Username, shipTo.Password); + return new CloudNodePool(shipTo.CloudId, basicAuthCredentials); + } + throw new Exception( + $"Cloud requires either '{nameof(ShipToOptions.ApiKey)}' or" + + $"'{nameof(ShipToOptions.Username)}' and '{nameof(ShipToOptions.Password)}" + ); default: - throw new ArgumentException($"Unrecognised connection pool type '{connectionPool}' specified in the configuration.", nameof(connectionPool)); + throw new ArgumentException($"Unrecognised connection pool type '{connectionPool}' specified in the configuration.", + nameof(connectionPool)); } } @@ -120,12 +130,29 @@ private static HttpTransport CreateTransport(ElasticsearchLoggerOptions loggerOp // TODO: Check if Uri has changed before recreating // TODO: Injectable factory? Or some way of testing. if (loggerOptions.Transport != null) return loggerOptions.Transport; + var connectionPool = CreateNodePool(loggerOptions); var config = new TransportConfiguration(connectionPool, productRegistration: new ElasticsearchProductRegistration()); + // Cloud sets authentication as required parameter in the constructor + if (loggerOptions.ShipTo.NodePoolType != NodePoolType.Cloud) + config = SetAuthenticationOnTransport(loggerOptions, config); + var transport = new DefaultHttpTransport(config); return transport; } + private static TransportConfiguration SetAuthenticationOnTransport(ElasticsearchLoggerOptions loggerOptions, TransportConfiguration config) + { + var apiKey = loggerOptions.ShipTo.ApiKey; + var username = loggerOptions.ShipTo.Username; + var password = loggerOptions.ShipTo.Password; + if (!username.IsNullOrEmpty() && !password.IsNullOrEmpty()) + config = config.Authentication(new BasicAuthentication(username, password)); + else if (!apiKey.IsNullOrEmpty()) + config = config.Authentication(new ApiKey(apiKey)); + return config; + } + private void ReloadShipper(ElasticsearchLoggerOptions loggerOptions) { var newShipper = CreatIngestChannel(loggerOptions); @@ -157,7 +184,7 @@ private IBufferedChannel CreatIngestChannel(ElasticsearchLoggerOptions WriteEvent = async (stream, ctx, logEvent) => await logEvent.SerializeAsync(stream, ctx).ConfigureAwait(false), }; SetupChannelOptions(_channelConfigurations, indexChannelOptions); - var channel = new EcsDataStreamChannel(indexChannelOptions); + var channel = new EcsDataStreamChannel(indexChannelOptions); channel.BootstrapElasticsearch(loggerOptions.BootstrapMethod); return channel; } diff --git a/src/Elastic.Extensions.Logging/Options/ShipToOptions.cs b/src/Elastic.Extensions.Logging/Options/ShipToOptions.cs index c4476fe4..1d2548f2 100644 --- a/src/Elastic.Extensions.Logging/Options/ShipToOptions.cs +++ b/src/Elastic.Extensions.Logging/Options/ShipToOptions.cs @@ -14,12 +14,12 @@ public class ShipToOptions /// /// Gets or sets the API Key, where connection pool type is Cloud, and authenticating via API Key. /// - public string ApiKey { get; set; } = ""; + public string? ApiKey { get; set; } /// /// Gets or sets the cloud ID, where connection pool type is Cloud. /// - public string CloudId { get; set; } = ""; + public string? CloudId { get; set; } /// /// Gets or sets the connection pool type. Default for multiple nodes is Sniffing; other supported values are @@ -36,11 +36,11 @@ public class ShipToOptions /// /// Gets or sets the password, where connection pool type is Cloud, and authenticating via username/password. /// - public string Password { get; set; } = ""; + public string? Password { get; set; } = ""; /// /// Gets or sets the username, where connection pool type is Cloud, and authenticating via username/password. /// - public string Username { get; set; } = ""; + public string? Username { get; set; } = ""; } } diff --git a/src/NullableExtensions.cs b/src/NullableExtensions.cs new file mode 100644 index 00000000..8c477151 --- /dev/null +++ b/src/NullableExtensions.cs @@ -0,0 +1,7 @@ +using System.Diagnostics.CodeAnalysis; + +namespace System; +internal static class NullableStringExtensions { + internal static bool IsNullOrEmpty([NotNullWhen(false)] this string? data) => + string.IsNullOrEmpty(data); +}