-
Notifications
You must be signed in to change notification settings - Fork 30
Opt-in to API key forwarding. Default to using SeqCli's connection se… #406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
ef0b9af
d3f43e0
bc031c9
b4b23e2
07ed32e
ae24fd3
5528c36
b7bfb80
97b3f66
5c3bd6d
67ba3fa
7a9f16d
377e7d4
6e56ff9
419d872
4b2010c
e033329
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Seq.Api; | ||
| using SeqCli.Config; | ||
| using SeqCli.Forwarder.Filesystem.System; | ||
| using SeqCli.Forwarder.Storage; | ||
| using Serilog; | ||
|
|
@@ -15,26 +16,33 @@ class ForwardingChannelMap | |
| { | ||
| readonly string _bufferPath; | ||
| readonly SeqConnection _connection; | ||
| readonly ForwardingChannel _defaultChannel; | ||
| readonly SeqCliConfig _config; | ||
| readonly string? _seqCliApiKey; | ||
|
|
||
| // Either seqcli is using its usual connection details and `_seqClieConnectionChannel` is the channel, | ||
| // or seqcli is using the incoming API key and there is one channel per API key (plus one for no API key) in the dictionary. | ||
| readonly Lock _channelsSync = new(); | ||
| readonly Dictionary<string, ForwardingChannel> _channels = new(); | ||
| ForwardingChannel? _seqCliConnectionChannel = null; | ||
| readonly Dictionary<string, ForwardingChannel> _channelsByName = new(); | ||
|
|
||
| readonly CancellationTokenSource _shutdownTokenSource = new(); | ||
| const string SeqCliConnectionChannelName = "SeqCliConnection"; | ||
|
|
||
| public ForwardingChannelMap(string bufferPath, SeqConnection connection, string? defaultApiKey) | ||
| public ForwardingChannelMap(string bufferPath, SeqConnection connection, SeqCliConfig config, string? seqCliApiKey) | ||
| { | ||
| _bufferPath = bufferPath; | ||
| _connection = connection; | ||
| _defaultChannel = OpenOrCreateChannel(defaultApiKey, "Default"); | ||
|
|
||
| // TODO, load other channels at start-up | ||
| _config = config; | ||
| _seqCliApiKey = seqCliApiKey; | ||
|
|
||
| LoadChannels(); | ||
| } | ||
|
|
||
| ForwardingChannel OpenOrCreateChannel(string? apiKey, string name) | ||
| { | ||
| // TODO, when it's not the default, persist the API key and validate equality on reopen | ||
|
|
||
| var storePath = Path.Combine(_bufferPath, name); | ||
| var storePath = GetStorePath(name); | ||
| var store = new SystemStoreDirectory(storePath); | ||
|
|
||
| Log.Information("Opening local buffer in {StorePath}", storePath); | ||
|
|
||
| return new ForwardingChannel( | ||
|
|
@@ -45,29 +53,78 @@ ForwardingChannel OpenOrCreateChannel(string? apiKey, string name) | |
| apiKey, | ||
| _shutdownTokenSource.Token); | ||
| } | ||
|
|
||
| public ForwardingChannel Get(string? apiKey) | ||
| void LoadChannels() | ||
| { | ||
| if (string.IsNullOrWhiteSpace(apiKey)) | ||
| if (_config.Forwarder.UseApiKeyForwarding) | ||
| { | ||
| return _defaultChannel; | ||
| foreach (var directoryPath in Directory.EnumerateDirectories(_bufferPath)) | ||
| { | ||
| if (directoryPath.Equals(GetStorePath(SeqCliConnectionChannelName))) | ||
| { | ||
| // data was stored when not using API key forwarding | ||
| continue; | ||
| } | ||
|
|
||
| var path = new SystemStoreDirectory(directoryPath); | ||
| var apiKey = path.ReadApiKey(_config); | ||
|
|
||
| var channelName = ApiKeyToName(apiKey); | ||
| var created = OpenOrCreateChannel(apiKey, channelName); | ||
| _channelsByName.Add(channelName, created); | ||
| } | ||
| } | ||
|
|
||
| else | ||
| { | ||
| _seqCliConnectionChannel = OpenOrCreateChannel(_seqCliApiKey, SeqCliConnectionChannelName); | ||
| } | ||
| } | ||
|
|
||
| string GetStorePath(string name) | ||
| { | ||
| return Path.Combine(_bufferPath, name); | ||
| } | ||
|
|
||
| public ForwardingChannel GetApiKeyForwardingChannel(string? requestApiKey) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should deny the use of |
||
| { | ||
| lock (_channelsSync) | ||
| { | ||
| if (_channels.TryGetValue(apiKey, out var channel)) | ||
| var channelName = ApiKeyToName(requestApiKey); | ||
|
|
||
| if (_channelsByName.TryGetValue(channelName, out var channel)) | ||
| { | ||
| return channel; | ||
| } | ||
|
|
||
| // Seq API keys begin with four identifying characters that aren't considered part of the | ||
| // confidential key. TODO: we could likely do better than this. | ||
| var name = apiKey[..4]; | ||
| var created = OpenOrCreateChannel(apiKey, name); | ||
| _channels.Add(apiKey, created); | ||
| var created = OpenOrCreateChannel(requestApiKey, channelName); | ||
| var store = new SystemStoreDirectory(GetStorePath(channelName)); | ||
| if (requestApiKey != null) | ||
| { | ||
| store.WriteApiKey(_config, requestApiKey); | ||
| } | ||
| _channelsByName.Add(channelName, created); | ||
| return created; | ||
| } | ||
| } | ||
|
|
||
| public ForwardingChannel GetSeqCliConnectionChannel() | ||
| { | ||
| lock (_channelsSync) | ||
| { | ||
| if (_seqCliConnectionChannel == null) | ||
| { | ||
| _seqCliConnectionChannel = OpenOrCreateChannel(_seqCliApiKey, SeqCliConnectionChannelName); | ||
| } | ||
| return _seqCliConnectionChannel; | ||
| } | ||
| } | ||
|
|
||
| string ApiKeyToName(string? apiKey) | ||
| { | ||
| // Seq API keys begin with four identifying characters that aren't considered part of the | ||
| // confidential key. TODO: we could likely do better than this. | ||
| return string.IsNullOrEmpty(apiKey) ? "EmptyApiKey" : apiKey[..(Math.Min(apiKey.Length, 4))]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| public async Task StopAsync() | ||
| { | ||
|
|
@@ -78,12 +135,15 @@ public async Task StopAsync() | |
| Task[] stopChannels; | ||
| lock (_channelsSync) | ||
| { | ||
| stopChannels = _channels.Values.Select(ch => ch.StopAsync()).ToArray(); | ||
| stopChannels = _channelsByName.Values.Select(ch => ch.StopAsync()).ToArray(); | ||
| } | ||
|
|
||
| if (_seqCliConnectionChannel != null) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the |
||
| { | ||
| stopChannels = stopChannels.Append(_seqCliConnectionChannel.StopAsync()).ToArray(); | ||
| } | ||
|
|
||
| await Task.WhenAll([ | ||
| _defaultChannel.StopAsync(), | ||
| ..stopChannels]); | ||
| await Task.WhenAll([..stopChannels]); | ||
|
|
||
| await _shutdownTokenSource.CancelAsync(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,9 @@ | |
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Runtime.InteropServices; | ||
| using System.Text; | ||
| using SeqCli.Config; | ||
| using Serilog; | ||
|
|
||
| #if UNIX | ||
| using SeqCli.Forwarder.Filesystem.System.Unix; | ||
|
|
@@ -34,6 +37,32 @@ public SystemStoreDirectory(string path) | |
| if (!Directory.Exists(_directoryPath)) Directory.CreateDirectory(_directoryPath); | ||
| } | ||
|
|
||
| public void WriteApiKey(SeqCliConfig config, string apiKey) | ||
| { | ||
| File.WriteAllBytes( | ||
| Path.Combine(_directoryPath, "api.key"), | ||
| config.Encryption.DataProtector().Encrypt(Encoding.UTF8.GetBytes(apiKey))); | ||
| } | ||
|
|
||
| public string? ReadApiKey(SeqCliConfig config) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| { | ||
| string? apiKey = null; | ||
| var path = Path.Combine(_directoryPath, "api.key"); | ||
|
|
||
| if (!File.Exists(path)) return apiKey; | ||
|
|
||
| try | ||
| { | ||
| var encrypted = File.ReadAllBytes(path); | ||
| apiKey = Encoding.UTF8.GetString(config.Encryption.DataProtector().Decrypt(encrypted)); | ||
| } | ||
| catch (Exception exception) | ||
| { | ||
| Log.Warning(exception, "Could not read or decrypt api key"); | ||
| } | ||
| return apiKey; | ||
| } | ||
|
|
||
| public override SystemStoreFile Create(string name) | ||
| { | ||
| var filePath = Path.Combine(_directoryPath, name); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the read fails, here, perhaps we should skip rather than fall through? Effects are going to be weird otherwise - e.g. the
Add()call on 74 will fail on subsequent keys that also fail.