Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
w1am committed Mar 19, 2024
1 parent 61c20e0 commit ad6b8f9
Show file tree
Hide file tree
Showing 16 changed files with 223 additions and 57 deletions.
13 changes: 11 additions & 2 deletions .github/workflows/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ on:
docker-tag:
required: true
type: string
docker-registry:
required: true
type: string
default: docker.eventstore.com/eventstore-ce/eventstoredb-ce
test-matrix:
required: true
type: string
default: '["Streams", "PersistentSubscriptions", "Operations", "UserManagement", "ProjectionManagement", "Plugins"]'

jobs:
test:
Expand All @@ -15,7 +23,7 @@ jobs:
matrix:
framework: [ net6.0, net7.0, net8.0 ]
os: [ ubuntu-latest ]
test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement ]
test: ${{fromJson(inputs.test-matrix)}}
configuration: [ release ]
runs-on: ${{ matrix.os }}
name: EventStore.Client.${{ matrix.test }}/${{ matrix.os }}/${{ matrix.framework }}/${{ inputs.docker-tag }}
Expand All @@ -36,7 +44,7 @@ jobs:
- name: Pull EventStore Image
shell: bash
run: |
docker pull docker.eventstore.com/eventstore-ce/eventstoredb-ce:${{ inputs.docker-tag }}
docker pull ${{ inputs.docker-registry }}:${{ inputs.docker-tag }}
- name: Install dotnet SDKs
uses: actions/setup-dotnet@v3
with:
Expand All @@ -52,6 +60,7 @@ jobs:
shell: bash
env:
ES_DOCKER_TAG: ${{ inputs.docker-tag }}
ES_DOCKER_REGISTRY: ${{ inputs.docker-registry }}
run: |
sudo ./gencert.sh
dotnet test --configuration ${{ matrix.configuration }} --blame \
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/enterprise.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Test CI

on:
pull_request:
push:
branches:
- master
tags:
- v*

jobs:
test:
uses: ./.github/workflows/base.yml
with:
docker-tag: 24.2.0-jammy
docker-registry: docker.eventstore.com/eventstore-ee/eventstoredb-commercial
test-matrix: '["Plugins"]'
secrets: inherit
14 changes: 6 additions & 8 deletions gencert.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ Write-Host ">> Generating certificate..."
New-Item -ItemType Directory -Path .\certs -Force

# Set permissions for the directory
icacls .\certs /grant:r "$($env:UserName):(OI)(CI)RX"
icacls .\certs /grant:r "$($env:UserName):(OI)(CI)F"

# Pull the Docker image
docker pull eventstore/es-gencert-cli:1.0.2
docker pull ghcr.io/eventstore/es-gencert-cli:1.3

# Create CA certificate
docker run --rm --volume ${PWD}\certs:/tmp --user (Get-Process -Id $PID).SessionId eventstore/es-gencert-cli:1.0.2 create-ca -out /tmp/ca

# Create node certificate
docker run --rm --volume ${PWD}\certs:/tmp --user (Get-Process -Id $PID).SessionId eventstore/es-gencert-cli:1.0.2 create-node -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/node -ip-addresses 127.0.0.1 -dns-names localhost
docker run --rm --volume ${PWD}\certs:/tmp ghcr.io/eventstore/es-gencert-cli create-ca -out /tmp/ca
docker run --rm --volume ${PWD}\certs:/tmp ghcr.io/eventstore/es-gencert-cli create-node -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/node -ip-addresses 127.0.0.1 -dns-names localhost
docker run --rm --volume ${PWD}\certs:/tmp ghcr.io/eventstore/es-gencert-cli create-user -username admin -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/user-admin

# Set permissions recursively for the directory
icacls .\certs /grant:r "$($env:UserName):(OI)(CI)RX"
icacls .\certs /grant:r "$($env:UserName):(OI)(CI)F"

Import-Certificate -FilePath ".\certs\ca\ca.crt" -CertStoreLocation Cert:\CurrentUser\Root
8 changes: 5 additions & 3 deletions gencert.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ mkdir -p certs

chmod 0755 ./certs

docker pull eventstore/es-gencert-cli:1.0.2
docker pull ghcr.io/eventstore/es-gencert-cli:1.3

docker run --rm --volume $PWD/certs:/tmp --user $(id -u):$(id -g) eventstore/es-gencert-cli:1.0.2 create-ca -out /tmp/ca
docker run --rm --volume $PWD/certs:/tmp --user $(id -u):$(id -g) ghcr.io/eventstore/es-gencert-cli create-ca -out /tmp/ca

docker run --rm --volume $PWD/certs:/tmp --user $(id -u):$(id -g) eventstore/es-gencert-cli:1.0.2 create-node -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/node -ip-addresses 127.0.0.1 -dns-names localhost
docker run --rm --volume $PWD/certs:/tmp --user $(id -u):$(id -g) ghcr.io/eventstore/es-gencert-cli create-node -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/node -ip-addresses 127.0.0.1 -dns-names localhost

docker run --rm --volume $PWD/certs:/tmp --user $(id -u):$(id -g) ghcr.io/eventstore/es-gencert-cli create-user -username admin -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/user-admin

chmod -R 0755 ./certs

Expand Down
2 changes: 1 addition & 1 deletion src/EventStore.Client.Common/EventStoreCallOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static CallOptions CreateNonStreaming(
Create(
settings,
deadline ?? settings.DefaultDeadline,
userCredentials?.ClientCertificate != null ? null : userCredentials,
userCredentials?.UserCertificate != null ? null : userCredentials,
cancellationToken
);

Expand Down
2 changes: 1 addition & 1 deletion src/EventStore.Client/CertificateUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace EventStore.Client;
/// <summary>
/// Utility class for loading certificates and private keys from files.
/// </summary>
public static class CertificateUtils {
static class CertificateUtils {
private static RSA LoadKey(string privateKeyPath) {
string[] allLines = File.ReadAllLines(privateKeyPath);
var header = allLines[0].Replace("-", "");
Expand Down
4 changes: 2 additions & 2 deletions src/EventStore.Client/ChannelFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ HttpMessageHandler CreateHandler() {

bool configureClientCert = settings.ConnectivitySettings.ClientCertificate != null
|| settings.ConnectivitySettings.TlsCaFile != null
|| channelIdentifier.UserCredentials?.ClientCertificate != null;
|| channelIdentifier.UserCredentials?.UserCertificate != null;

var certificate = channelIdentifier.UserCredentials?.ClientCertificate
var certificate = channelIdentifier.UserCredentials?.UserCertificate
?? settings.ConnectivitySettings.ClientCertificate
?? settings.ConnectivitySettings.TlsCaFile;

Expand Down
4 changes: 2 additions & 2 deletions src/EventStore.Client/HttpFallback.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ internal HttpFallback (EventStoreClientSettings settings, UserCredentials? userC

bool configureClientCert = settings.ConnectivitySettings.ClientCertificate != null
|| settings.ConnectivitySettings.TlsCaFile != null
|| userCredentials?.ClientCertificate != null;
|| userCredentials?.UserCertificate != null;

var certificate = userCredentials?.ClientCertificate
var certificate = userCredentials?.UserCertificate
?? settings.ConnectivitySettings.ClientCertificate
?? settings.ConnectivitySettings.TlsCaFile;

Expand Down
59 changes: 28 additions & 31 deletions src/EventStore.Client/SingleNodeChannelSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,32 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace EventStore.Client {
internal class SingleNodeChannelSelector : IChannelSelector {
private readonly ILogger _log;
private readonly ChannelCache _channelCache;
private readonly ChannelIdentifier _channelIdentifier;

public SingleNodeChannelSelector(
EventStoreClientSettings settings,
ChannelCache channelCache) {

_log = settings.LoggerFactory?.CreateLogger<SingleNodeChannelSelector>() ??
new NullLogger<SingleNodeChannelSelector>();

_channelCache = channelCache;

var uri = settings.ConnectivitySettings.ResolvedAddressOrDefault;

_channelIdentifier = new ChannelIdentifier(new DnsEndPoint(uri.Host, uri.Port));
}

public Task<ChannelBase> SelectChannelAsync(
UserCredentials? userCredentials, CancellationToken cancellationToken
) =>
Task.FromResult(SelectChannel(_channelIdentifier));

public ChannelBase SelectChannel(ChannelIdentifier channelIdentifier) {
_log.LogInformation("Selected {endPoint}.", channelIdentifier);

return _channelCache.GetChannelInfo(channelIdentifier);
}
}
namespace EventStore.Client
{
internal class SingleNodeChannelSelector : IChannelSelector
{
private readonly ILogger _log;
private readonly ChannelCache _channelCache;
private readonly EventStoreClientSettings _settings;

public SingleNodeChannelSelector(EventStoreClientSettings settings, ChannelCache channelCache)
{
_log = settings.LoggerFactory?.CreateLogger<SingleNodeChannelSelector>() ?? new NullLogger<SingleNodeChannelSelector>();
_settings = settings;
_channelCache = channelCache;
}

public Task<ChannelBase> SelectChannelAsync(UserCredentials? userCredentials, CancellationToken cancellationToken)
{
var uri = _settings.ConnectivitySettings.ResolvedAddressOrDefault;
var channelIdentifier = new ChannelIdentifier(new DnsEndPoint(uri.Host, uri.Port), userCredentials);
return Task.FromResult(SelectChannel(channelIdentifier));
}

public ChannelBase SelectChannel(ChannelIdentifier channelIdentifier)
{
_log.LogInformation("Selected {endPoint}.", channelIdentifier);
return _channelCache.GetChannelInfo(channelIdentifier);
}
}
}
30 changes: 30 additions & 0 deletions src/EventStore.Client/UserCertificate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Security.Cryptography.X509Certificates;

namespace EventStore.Client {
/// <summary>
/// Represents the user certificates used to authenticate and authorize operations on the EventStoreDB.
/// </summary>
public class UserCertificate {
/// <summary>
/// The user certificate
/// </summary>
public X509Certificate2? Certificate { get; }

/// <summary>
/// Constructs a new <see cref="UserCredentials"/>.
/// </summary>
public UserCertificate(X509Certificate2 userCertificate) {
Certificate = userCertificate;
}

/// <summary>
/// Constructs a new <see cref="UserCredentials"/>.
/// </summary>
public UserCertificate(string certificatePath, string privateKeyPath) {
Certificate = CertificateUtils.LoadFromFile(
certificatePath,
privateKeyPath
);
}
}
}
8 changes: 4 additions & 4 deletions src/EventStore.Client/UserCredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public class UserCredentials {
/// <summary>
/// Constructs a new <see cref="UserCredentials"/>.
/// </summary>
public UserCredentials(X509Certificate2 clientCertificate) {
ClientCertificate = clientCertificate;
public UserCredentials(UserCertificate userCertificate) {
UserCertificate = userCertificate.Certificate;
}

/// <summary>
Expand Down Expand Up @@ -44,7 +44,7 @@ public UserCredentials(string bearerToken) {
/// <summary>
/// The client certificate
/// </summary>
public X509Certificate2? ClientCertificate { get; }
public X509Certificate2? UserCertificate { get; }

/// <summary>
/// The username
Expand All @@ -58,7 +58,7 @@ public UserCredentials(string bearerToken) {

/// <inheritdoc />
public override string ToString() =>
ClientCertificate != null ? string.Empty : Authorization?.ToString() ?? string.Empty;
UserCertificate != null ? string.Empty : Authorization?.ToString() ?? string.Empty;

/// <summary>
/// Implicitly convert a <see cref="UserCredentials"/> to a <see cref="string"/>.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\EventStore.Client.Tests.Common\EventStore.Client.Tests.Common.csproj"/>
</ItemGroup>
</Project>
95 changes: 95 additions & 0 deletions test/EventStore.Client.Plugins.Tests/UserCertificateTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Grpc.Core;

namespace EventStore.Client.Plugins.Tests {
[Trait("Category", "Certificates")]
public class UserCertificateTests(ITestOutputHelper output, EventStoreFixture fixture) : EventStoreTests<EventStoreFixture>(output, fixture) {
[Fact]
public async Task user_credentials_takes_precedence_over_user_certificates()
{
var certPath = Path.Combine("certs", "user-admin", "user-admin.crt");
var certKeyPath = Path.Combine("certs", "user-admin", "user-admin.key");

var connectionString =
$"esdb://localhost:2113/?tls=true&tlsVerifyCert=true&certPath={certPath}&certKeyPath={certKeyPath}";

var stream = Fixture.GetStreamName();

var settings = EventStoreClientSettings.Create(connectionString);

var client = new EventStoreClient(settings);

await Assert.ThrowsAsync<NotAuthenticatedException>(
() => client.AppendToStreamAsync(
stream,
StreamState.Any,
Enumerable.Empty<EventData>(),
userCredentials: TestCredentials.TestBadUser
)
);
}

[Fact]
public Task does_not_accept_certificates_with_invalid_path()
{
var certPath = Path.Combine("certs", "invalid", "invalid.crt");
var certKeyPath = Path.Combine("certs", "invalid", "invalid.key");

var connectionString =
$"esdb://admin:changeit@localhost:2113/?tls=true&tlsVerifyCert=true&certPath={certPath}&certKeyPath={certKeyPath}";

Assert.Throws<InvalidSettingException>(() => EventStoreClientSettings.Create(connectionString) );

return Task.CompletedTask;
}

[Fact]
public async Task append_should_be_successful_with_user_certificates()
{
var certPath = Path.Combine(Environment.CurrentDirectory, "certs", "user-admin", "user-admin.crt");
var certKeyPath = Path.Combine(Environment.CurrentDirectory, "certs", "user-admin", "user-admin.key");

Assert.True(File.Exists(certPath));
Assert.True(File.Exists(certKeyPath));

var connectionString =
$"esdb://localhost:2113/?tls=true&tlsVerifyCert=true&certPath={certPath}&certKeyPath={certKeyPath}";

Fixture.Log.Information("connectionString: {connectionString}", connectionString);

var stream = Fixture.GetStreamName();

var settings = EventStoreClientSettings.Create(connectionString);

var client = new EventStoreClient(settings);

var result = await client.AppendToStreamAsync(
stream,
StreamState.Any,
Enumerable.Empty<EventData>()
);

Assert.NotNull(result);
}

[Fact]
public async Task append_with_overriden_user_certificates_should_pass()
{
var connectionString = "esdb://admin:changeit@localhost:2113/?tls=true&tlsVerifyCert=true";

var stream = Fixture.GetStreamName();

var settings = EventStoreClientSettings.Create(connectionString);

var client = new EventStoreClient(settings);

var result = await client.AppendToStreamAsync(
stream,
StreamState.Any,
Enumerable.Empty<EventData>(),
userCredentials: TestCredentials.UserAdminCertificate
);

Assert.NotNull(result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ public static EventStoreFixtureOptions DefaultOptions() {
["EVENTSTORE_DISABLE_LOG_FILE"] = "true",
["EVENTSTORE_ADVERTISE_HTTP_PORT_TO_CLIENT_AS"] = $"{NetworkPortProvider.DefaultEsdbPort}"
};


if (GlobalEnvironment.DockerImage.Contains("commercial")) {
defaultEnvironment["EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH"] = "/etc/eventstore/certs/ca";
defaultEnvironment["EventStore__Plugins__UserCertificates__Enabled"] = "true";
}

// TODO SS: must find a way to enable parallel tests on CI. It works locally.
if (port != NetworkPortProvider.DefaultEsdbPort) {
if (GlobalEnvironment.Variables.TryGetValue("ES_DOCKER_TAG", out var tag) && tag == "ci")
Expand Down
2 changes: 1 addition & 1 deletion test/EventStore.Client.Tests.Common/GlobalEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ static void EnsureDefaults(IConfiguration configuration) {
configuration.EnsureValue("ES_USE_CLUSTER", "false");
configuration.EnsureValue("ES_USE_EXTERNAL_SERVER", "false");

configuration.EnsureValue("ES_DOCKER_REGISTRY", "docker.eventstore.com/eventstore-ce/eventstoredb-ce");
configuration.EnsureValue("ES_DOCKER_REGISTRY", "ghcr.io/eventstore/eventstore");
configuration.EnsureValue("ES_DOCKER_TAG", "ci");
configuration.EnsureValue("ES_DOCKER_IMAGE", $"{configuration["ES_DOCKER_REGISTRY"]}:{configuration["ES_DOCKER_TAG"]}");

Expand Down
Loading

0 comments on commit ad6b8f9

Please sign in to comment.