diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..c4d1853ee
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,43 @@
+
+What
+----
+
+
+Checklist
+------------------
+- [ ] Contains customer facing changes? Including API/behavior changes
+- [ ] Did you add sufficient unit test and/or integration test coverage for this PR?
+ - If not, please explain why it is not required
+
+References
+----------
+JIRA:
+
+
+Test & Review
+------------
+
+
+Open questions / Follow-ups
+--------------------------
+
+
+
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 000000000..f6105fb6e
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,98 @@
+name: 'confluent-kafka-dotnet build pipeline'
+
+env:
+ CONFIGURATION: Release
+ DOTNET_CLI_TELEMETRY_OPTOUT: 'true'
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ linux-build:
+ name: 'Linux x64'
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.x'
+ - name: Build and test
+ run: |
+ dotnet restore
+ make build
+ make test
+
+ osx-build:
+ name: 'OSX x64'
+ runs-on: macos-13
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.x'
+ - name: Set ulimit
+ run: ulimit -n 1024
+ - name: Build and test
+ run: |
+ dotnet restore
+ make build
+ make test
+
+ windows-build:
+ name: 'Windows x64'
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.403'
+ - name: Build and test
+ run: |
+ dotnet restore
+ dotnet test -c $env:CONFIGURATION test/Confluent.Kafka.UnitTests/Confluent.Kafka.UnitTests.csproj
+ # dotnet test -c $env:CONFIGURATION test/Confluent.SchemaRegistry.UnitTests/Confluent.SchemaRegistry.UnitTests.csproj
+ # dotnet test -c $env:CONFIGURATION test/Confluent.SchemaRegistry.Serdes.UnitTests/Confluent.SchemaRegistry.Serdes.UnitTests.csproj
+
+ windows-artifacts:
+ name: 'Windows Artifacts'
+ needs: windows-build
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.403'
+ - name: Install DocFX
+ run: dotnet tool update -g docfx
+ - name: Build and create packages
+ run: |
+ dotnet restore
+ dotnet build Confluent.Kafka.sln -c $env:CONFIGURATION
+
+ # Different packaging for tagged vs untagged builds
+ if ($env:GITHUB_REF -match '^refs/tags/') {
+ $suffix = ""
+ $vsuffix = ""
+ } else {
+ $suffix = "ci-$env:GITHUB_RUN_ID"
+ $vsuffix = "--version-suffix"
+ }
+
+ dotnet pack src/Confluent.Kafka/Confluent.Kafka.csproj --output artifacts -c $env:CONFIGURATION $vsuffix $suffix
+ # dotnet pack src/Confluent.SchemaRegistry/Confluent.SchemaRegistry.csproj -c $env:CONFIGURATION $suffix --output artifacts
+ # dotnet pack src/Confluent.SchemaRegistry.Serdes.Avro/Confluent.SchemaRegistry.Serdes.Avro.csproj -c $env:CONFIGURATION $suffix --output artifacts
+ # dotnet pack src/Confluent.SchemaRegistry.Serdes.Protobuf/Confluent.SchemaRegistry.Serdes.Protobuf.csproj -c $env:CONFIGURATION $suffix --output artifacts
+ # dotnet pack src/Confluent.SchemaRegistry.Serdes.Json/Confluent.SchemaRegistry.Serdes.Json.csproj -c $env:CONFIGURATION $suffix --output artifacts
+
+ # docfx doc/docfx.json
+ # tar -czf artifacts/docs-$env:GITHUB_RUN_ID.zip doc/_site/*
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: build-artifacts
+ path: artifacts/
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
new file mode 100644
index 000000000..65a970b62
--- /dev/null
+++ b/.github/workflows/integration.yml
@@ -0,0 +1,60 @@
+name: 'Integration Tests'
+
+env:
+ CONFIGURATION: Release
+ DOTNET_CLI_TELEMETRY_OPTOUT: 'true'
+ SEMAPHORE_SKIP_FLAKY_TESTS: 'true'
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ integration-tests:
+ name: 'Integration tests'
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.x'
+ - name: Login to DockerHub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USER }}
+ password: ${{ secrets.DOCKERHUB_APIKEY }}
+
+ - name: Classic Protocol Tests
+ run: |
+ cd test/docker && docker-compose up -d && sleep 30 && cd ../..
+ dotnet restore
+ cd test/Confluent.Kafka.IntegrationTests && dotnet test -l "console;verbosity=normal" && cd ../..
+
+ - name: Consumer Protocol Tests
+ run: |
+ cd test/docker && docker-compose -f docker-compose-kraft.yaml up -d && cd ../..
+ sleep 300
+ export TEST_CONSUMER_GROUP_PROTOCOL=consumer
+ dotnet restore
+ cd test/Confluent.Kafka.IntegrationTests && dotnet test -l "console;verbosity=normal" && cd ../..
+
+ schema-registry-tests:
+ name: 'Schema registry and serdes integration tests'
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.x'
+ - name: Login to DockerHub
+ uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.DOCKERHUB_USER }}
+ password: ${{ secrets.DOCKERHUB_APIKEY }}
+ - name: Run Tests
+ run: |
+ cd test/docker && docker-compose up -d && cd ../..
+ dotnet restore
+ cd test/Confluent.SchemaRegistry.Serdes.IntegrationTests && dotnet test -l "console;verbosity=normal" && cd ../..
\ No newline at end of file
diff --git a/3RD_PARTY.md b/3RD_PARTY.md
index 6a4a20f57..df00ef05c 100644
--- a/3RD_PARTY.md
+++ b/3RD_PARTY.md
@@ -10,3 +10,4 @@ To add your project, open a pull request!
- [Multi Schema Avro Deserializer](https://github.com/ycherkes/multi-schema-avro-desrializer) - Avro deserializer for reading messages serialized with multiple schemas.
- [OpenSleigh.Transport.Kafka](https://github.com/mizrael/OpenSleigh/tree/develop/src/OpenSleigh.Transport.Kafka) - A Kafka Transport for OpenSleigh, a distributed saga management library.
- [SlimMessageBus.Host.Kafka](https://github.com/zarusz/SlimMessageBus) - Apache Kafka transport for SlimMessageBus (lightweight message bus for .NET)
+- [Kafka Core](https://github.com/ffernandolima/confluent-kafka-core-dotnet) - Kafka Core empowers developers to build robust .NET applications on top of Confluent Kafka, focusing on simplicity, maintainability, and extensibility with intuitive abstractions and builders.
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae3755874..8d3377608 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,17 @@
## Enhancements
+* References librdkafka.redist 2.6.1. Refer to the [librdkafka v2.6.1 release notes](https://github.com/confluentinc/librdkafka/releases/tag/v2.6.1) for more information.
+
+## Fixes
+
* Fix to continue supporting .NET Framework 4.6.2+ in core client library (#2342).
+* Fix JSON Schema handling to not require use of `$id` (#2339).
+* Update Caching.Memory to 8.0.1 to address CVE (#23440.
+* Added Qualified and Custom reference name strategy approaches for protobuf references (#2345).
+* Fix validate of SSL CA certs in Schema Registry client (#2346).
+* Skip SSL certs validation when configured in Schema Registry client (#2347).
+* Allow proxy to be specified in Schema Registry client (#2348).
# 2.6.0
diff --git a/src/Confluent.Kafka/Confluent.Kafka.csproj b/src/Confluent.Kafka/Confluent.Kafka.csproj
index 4c9467ddc..39bcabed4 100644
--- a/src/Confluent.Kafka/Confluent.Kafka.csproj
+++ b/src/Confluent.Kafka/Confluent.Kafka.csproj
@@ -12,16 +12,14 @@
https://raw.githubusercontent.com/confluentinc/confluent-kafka-dotnet/master/confluent-logo.png
https://github.com/confluentinc/confluent-kafka-dotnet/releases
Kafka;Confluent;librdkafka
- Confluent.Kafka
+ Confluent.Kafka.MK
README.md
Confluent.Kafka
- Confluent.Kafka
+ Confluent.Kafka.MK
2.6.1
netstandard2.0;net462;net6.0;net8.0
true
true
- true
- Confluent.Kafka.snk
diff --git a/src/Confluent.SchemaRegistry.Encryption.Aws/AwsKmsClient.cs b/src/Confluent.SchemaRegistry.Encryption.Aws/AwsKmsClient.cs
index 0b4a0ff94..5a3c14ac8 100644
--- a/src/Confluent.SchemaRegistry.Encryption.Aws/AwsKmsClient.cs
+++ b/src/Confluent.SchemaRegistry.Encryption.Aws/AwsKmsClient.cs
@@ -12,7 +12,7 @@ public class AwsKmsClient : IKmsClient
{
private AmazonKeyManagementServiceClient kmsClient;
private string keyId;
-
+
public string KekId { get; }
public AwsKmsClient(string kekId, AWSCredentials credentials)
@@ -36,7 +36,7 @@ public AwsKmsClient(string kekId, AWSCredentials credentials)
public bool DoesSupport(string uri)
{
- return uri.StartsWith(AwsKmsDriver.Prefix);
+ return KekId == uri;
}
public async Task Encrypt(byte[] plaintext)
diff --git a/src/Confluent.SchemaRegistry.Encryption.Azure/AzureKmsClient.cs b/src/Confluent.SchemaRegistry.Encryption.Azure/AzureKmsClient.cs
index 2ef3f1cd7..1a70524c6 100644
--- a/src/Confluent.SchemaRegistry.Encryption.Azure/AzureKmsClient.cs
+++ b/src/Confluent.SchemaRegistry.Encryption.Azure/AzureKmsClient.cs
@@ -25,7 +25,7 @@ public AzureKmsClient(string kekId, TokenCredential tokenCredential)
public bool DoesSupport(string uri)
{
- return uri.StartsWith(AzureKmsDriver.Prefix);
+ return KekId == uri;
}
public async Task Encrypt(byte[] plaintext)
diff --git a/src/Confluent.SchemaRegistry.Encryption.Gcp/GcpKmsClient.cs b/src/Confluent.SchemaRegistry.Encryption.Gcp/GcpKmsClient.cs
index db5fd4683..c3a995bfe 100644
--- a/src/Confluent.SchemaRegistry.Encryption.Gcp/GcpKmsClient.cs
+++ b/src/Confluent.SchemaRegistry.Encryption.Gcp/GcpKmsClient.cs
@@ -36,7 +36,7 @@ public GcpKmsClient(string kekId, GoogleCredential credential)
public bool DoesSupport(string uri)
{
- return uri.StartsWith(GcpKmsDriver.Prefix);
+ return KekId == uri;
}
public async Task Encrypt(byte[] plaintext)
diff --git a/src/Confluent.SchemaRegistry.Encryption.HcVault/HcVaultKmsClient.cs b/src/Confluent.SchemaRegistry.Encryption.HcVault/HcVaultKmsClient.cs
index 128a531b1..781af5714 100644
--- a/src/Confluent.SchemaRegistry.Encryption.HcVault/HcVaultKmsClient.cs
+++ b/src/Confluent.SchemaRegistry.Encryption.HcVault/HcVaultKmsClient.cs
@@ -21,11 +21,6 @@ public class HcVaultKmsClient : IKmsClient
public HcVaultKmsClient(string kekId, string ns, string tokenId)
{
- if (tokenId == null)
- {
- tokenId = Environment.GetEnvironmentVariable("VAULT_TOKEN");
- ns = Environment.GetEnvironmentVariable("VAULT_NAMESPACE");
- }
KekId = kekId;
Namespace = ns;
TokenId = tokenId;
@@ -53,7 +48,7 @@ public HcVaultKmsClient(string kekId, string ns, string tokenId)
public bool DoesSupport(string uri)
{
- return uri.StartsWith(HcVaultKmsDriver.Prefix);
+ return KekId == uri;
}
public async Task Encrypt(byte[] plaintext)
diff --git a/src/Confluent.SchemaRegistry.Encryption.HcVault/HcVaultKmsDriver.cs b/src/Confluent.SchemaRegistry.Encryption.HcVault/HcVaultKmsDriver.cs
index e220afc7d..b2cfd17ca 100644
--- a/src/Confluent.SchemaRegistry.Encryption.HcVault/HcVaultKmsDriver.cs
+++ b/src/Confluent.SchemaRegistry.Encryption.HcVault/HcVaultKmsDriver.cs
@@ -23,6 +23,11 @@ public IKmsClient NewKmsClient(IDictionary config, string keyUrl
{
config.TryGetValue(TokenId, out string tokenId);
config.TryGetValue(Namespace, out string ns);
+ if (tokenId == null)
+ {
+ tokenId = Environment.GetEnvironmentVariable("VAULT_TOKEN");
+ ns = Environment.GetEnvironmentVariable("VAULT_NAMESPACE");
+ }
return new HcVaultKmsClient(keyUrl, ns, tokenId);
}
}
diff --git a/src/Confluent.SchemaRegistry.Encryption/CachedDekRegistryClient.cs b/src/Confluent.SchemaRegistry.Encryption/CachedDekRegistryClient.cs
index cbd631b5f..f11798380 100644
--- a/src/Confluent.SchemaRegistry.Encryption/CachedDekRegistryClient.cs
+++ b/src/Confluent.SchemaRegistry.Encryption/CachedDekRegistryClient.cs
@@ -18,7 +18,7 @@
using System.Threading.Tasks;
using System.Linq;
using System;
-using System.ComponentModel;
+using System.Net;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
@@ -31,7 +31,7 @@ public record DekId(string KekName, string Subject, int? Version, DekFormat? Dek
///
/// A caching DEK Registry client.
///
- public class CachedDekRegistryClient : IDekRegistryClient, IDisposable
+ public class CachedDekRegistryClient : IDekRegistryClient
{
private DekRestService restService;
@@ -48,6 +48,21 @@ public class CachedDekRegistryClient : IDekRegistryClient, IDisposable
///
public const int DefaultTimeout = 30000;
+ ///
+ /// The default maximum number of retries.
+ ///
+ public const int DefaultMaxRetries = RestService.DefaultMaxRetries;
+
+ ///
+ /// The default time to wait for the first retry.
+ ///
+ public const int DefaultRetriesWaitMs = RestService.DefaultRetriesWaitMs;
+
+ ///
+ /// The default time to wait for any retry.
+ ///
+ public const int DefaultRetriesMaxWaitMs = RestService.DefaultRetriesMaxWaitMs;
+
///
/// The default maximum capacity of the local cache.
///
@@ -71,12 +86,16 @@ public int MaxCachedKeys
///
/// The authentication header value provider
///
+ ///
+ /// The proxy server to use for connections
+ ///
public CachedDekRegistryClient(IEnumerable> config,
- IAuthenticationHeaderValueProvider authenticationHeaderValueProvider)
+ IAuthenticationHeaderValueProvider authenticationHeaderValueProvider,
+ IWebProxy proxy = null)
{
if (config == null)
{
- throw new ArgumentNullException("config properties must be specified.");
+ throw new ArgumentNullException("config");
}
var schemaRegistryUrisMaybe = config.FirstOrDefault(prop =>
prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryUrl);
@@ -101,6 +120,45 @@ public CachedDekRegistryClient(IEnumerable> config,
$"Configured value for {SchemaRegistryConfig.PropertyNames.SchemaRegistryRequestTimeoutMs} must be an integer.");
}
+ var maxRetriesMaybe = config.FirstOrDefault(prop =>
+ prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxRetries);
+ int maxRetries;
+ try
+ {
+ maxRetries = maxRetriesMaybe.Value == null ? DefaultMaxRetries : Convert.ToInt32(maxRetriesMaybe.Value);
+ }
+ catch (FormatException)
+ {
+ throw new ArgumentException(
+ $"Configured value for {SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxRetries} must be an integer.");
+ }
+
+ var retriesWaitMsMaybe = config.FirstOrDefault(prop =>
+ prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryRetriesWaitMs);
+ int retriesWaitMs;
+ try
+ {
+ retriesWaitMs = retriesWaitMsMaybe.Value == null ? DefaultRetriesWaitMs : Convert.ToInt32(retriesWaitMsMaybe.Value);
+ }
+ catch (FormatException)
+ {
+ throw new ArgumentException(
+ $"Configured value for {SchemaRegistryConfig.PropertyNames.SchemaRegistryRetriesWaitMs} must be an integer.");
+ }
+
+ var retriesMaxWaitMsMaybe = config.FirstOrDefault(prop =>
+ prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryRetriesMaxWaitMs);
+ int retriesMaxWaitMs;
+ try
+ {
+ retriesMaxWaitMs = retriesMaxWaitMsMaybe.Value == null ? DefaultRetriesMaxWaitMs : Convert.ToInt32(retriesMaxWaitMsMaybe.Value);
+ }
+ catch (FormatException)
+ {
+ throw new ArgumentException(
+ $"Configured value for {SchemaRegistryConfig.PropertyNames.SchemaRegistryRetriesMaxWaitMs} must be an integer.");
+ }
+
var identityMapCapacityMaybe = config.FirstOrDefault(prop =>
prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxCachedSchemas);
try
@@ -206,6 +264,9 @@ public CachedDekRegistryClient(IEnumerable> config,
if (property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryUrl &&
property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryRequestTimeoutMs &&
+ property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxRetries &&
+ property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryRetriesWaitMs &&
+ property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryRetriesMaxWaitMs &&
property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxCachedSchemas &&
property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryLatestCacheTtlSecs &&
property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource &&
@@ -236,8 +297,10 @@ public CachedDekRegistryClient(IEnumerable> config,
$"Configured value for {SchemaRegistryConfig.PropertyNames.EnableSslCertificateVerification} must be a bool.");
}
+ var sslCaLocation = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SslCaLocation).Value;
+ var sslCaCertificate = string.IsNullOrEmpty(sslCaLocation) ? null : new X509Certificate2(sslCaLocation);
this.restService = new DekRestService(schemaRegistryUris, timeoutMs, authenticationHeaderValueProvider,
- SetSslConfig(config), sslVerify);
+ SetSslConfig(config), sslVerify, sslCaCertificate, proxy, maxRetries, retriesWaitMs, retriesMaxWaitMs);
}
///
@@ -291,14 +354,6 @@ private List SetSslConfig(IEnumerable prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SslCaLocation)
- .Value ?? "";
- if (!String.IsNullOrEmpty(caLocation))
- {
- certificates.Add(new X509Certificate2(caLocation));
- }
-
return certificates;
}
diff --git a/src/Confluent.SchemaRegistry.Encryption/FieldEncryptionExecutor.cs b/src/Confluent.SchemaRegistry.Encryption/FieldEncryptionExecutor.cs
index 3ec805e56..5b8e0c7de 100644
--- a/src/Confluent.SchemaRegistry.Encryption/FieldEncryptionExecutor.cs
+++ b/src/Confluent.SchemaRegistry.Encryption/FieldEncryptionExecutor.cs
@@ -45,12 +45,20 @@ public FieldEncryptionExecutor(IDekRegistryClient client, IClock clock)
Clock = clock ?? new Clock();
}
- public override void Configure(IEnumerable> config)
+ public override void Configure(IEnumerable> config,
+ ISchemaRegistryClient client = null)
{
Configs = config;
if (Client == null)
{
- Client = new CachedDekRegistryClient(Configs);
+ if (client != null)
+ {
+ Client = new CachedDekRegistryClient(Configs, client.AuthHeaderProvider, client.Proxy);
+ }
+ else
+ {
+ Client = new CachedDekRegistryClient(Configs);
+ }
}
}
diff --git a/src/Confluent.SchemaRegistry.Encryption/LocalKmsClient.cs b/src/Confluent.SchemaRegistry.Encryption/LocalKmsClient.cs
index 66afb08ef..5b05dac89 100644
--- a/src/Confluent.SchemaRegistry.Encryption/LocalKmsClient.cs
+++ b/src/Confluent.SchemaRegistry.Encryption/LocalKmsClient.cs
@@ -16,14 +16,6 @@ public class LocalKmsClient : IKmsClient
public LocalKmsClient(string secret)
{
- if (secret == null)
- {
- secret = Environment.GetEnvironmentVariable("LOCAL_SECRET");
- }
- if (secret == null)
- {
- throw new ArgumentNullException("Cannot load secret");
- }
Secret = secret;
cryptor = new Cryptor(DekFormat.AES128_GCM);
byte[] rawKey = Hkdf.DeriveKey(
diff --git a/src/Confluent.SchemaRegistry.Encryption/LocalKmsDriver.cs b/src/Confluent.SchemaRegistry.Encryption/LocalKmsDriver.cs
index 87ad7bd91..2def8c054 100644
--- a/src/Confluent.SchemaRegistry.Encryption/LocalKmsDriver.cs
+++ b/src/Confluent.SchemaRegistry.Encryption/LocalKmsDriver.cs
@@ -22,6 +22,14 @@ public string GetKeyUrlPrefix()
public IKmsClient NewKmsClient(IDictionary config, string keyUrl)
{
config.TryGetValue(Secret, out string secret);
+ if (secret == null)
+ {
+ secret = Environment.GetEnvironmentVariable("LOCAL_SECRET");
+ }
+ if (secret == null)
+ {
+ throw new ArgumentNullException("Cannot load secret");
+ }
return new LocalKmsClient(secret);
}
}
diff --git a/src/Confluent.SchemaRegistry.Encryption/Rest/DekRestService.cs b/src/Confluent.SchemaRegistry.Encryption/Rest/DekRestService.cs
index be7b2bc2c..ebda3d756 100644
--- a/src/Confluent.SchemaRegistry.Encryption/Rest/DekRestService.cs
+++ b/src/Confluent.SchemaRegistry.Encryption/Rest/DekRestService.cs
@@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
+using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
@@ -29,9 +30,11 @@ public class DekRestService : RestService
///
public DekRestService(string schemaRegistryUrl, int timeoutMs,
IAuthenticationHeaderValueProvider authenticationHeaderValueProvider, List certificates,
- bool enableSslCertificateVerification) :
+ bool enableSslCertificateVerification, X509Certificate2 sslCaCertificate = null, IWebProxy proxy = null,
+ int maxRetries = DefaultMaxRetries, int retriesWaitMs = DefaultRetriesWaitMs,
+ int retriesMaxWaitMs = DefaultRetriesMaxWaitMs) :
base(schemaRegistryUrl, timeoutMs, authenticationHeaderValueProvider, certificates,
- enableSslCertificateVerification)
+ enableSslCertificateVerification, sslCaCertificate, proxy, maxRetries, retriesWaitMs, retriesMaxWaitMs)
{
}
diff --git a/src/Confluent.SchemaRegistry.Rules/CelExecutor.cs b/src/Confluent.SchemaRegistry.Rules/CelExecutor.cs
index b3843c3e2..346607a59 100644
--- a/src/Confluent.SchemaRegistry.Rules/CelExecutor.cs
+++ b/src/Confluent.SchemaRegistry.Rules/CelExecutor.cs
@@ -34,7 +34,8 @@ public CelExecutor()
{
}
- public void Configure(IEnumerable> config)
+ public void Configure(IEnumerable> config,
+ ISchemaRegistryClient client = null)
{
}
diff --git a/src/Confluent.SchemaRegistry.Rules/CelFieldExecutor.cs b/src/Confluent.SchemaRegistry.Rules/CelFieldExecutor.cs
index c51c71c9b..5cd329a20 100644
--- a/src/Confluent.SchemaRegistry.Rules/CelFieldExecutor.cs
+++ b/src/Confluent.SchemaRegistry.Rules/CelFieldExecutor.cs
@@ -22,7 +22,8 @@ public CelFieldExecutor()
public override string Type() => RuleType;
- public override void Configure(IEnumerable> config)
+ public override void Configure(IEnumerable> config,
+ ISchemaRegistryClient client = null)
{
}
diff --git a/src/Confluent.SchemaRegistry.Rules/JsonataExecutor.cs b/src/Confluent.SchemaRegistry.Rules/JsonataExecutor.cs
index 33dd64b5f..c34fe9a4a 100644
--- a/src/Confluent.SchemaRegistry.Rules/JsonataExecutor.cs
+++ b/src/Confluent.SchemaRegistry.Rules/JsonataExecutor.cs
@@ -20,7 +20,8 @@ public JsonataExecutor()
{
}
- public void Configure(IEnumerable> config)
+ public void Configure(IEnumerable> config,
+ ISchemaRegistryClient client = null)
{
}
diff --git a/src/Confluent.SchemaRegistry.Serdes.Avro/GenericDeserializerImpl.cs b/src/Confluent.SchemaRegistry.Serdes.Avro/GenericDeserializerImpl.cs
index 6d026fa0a..96b938973 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Avro/GenericDeserializerImpl.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Avro/GenericDeserializerImpl.cs
@@ -72,7 +72,7 @@ public async Task Deserialize(string topic, Headers headers, byte
}
string subject = GetSubjectName(topic, isKey, null);
- Schema latestSchema = null;
+ RegisteredSchema latestSchema = null;
if (subject != null)
{
latestSchema = await GetReaderSchema(subject)
diff --git a/src/Confluent.SchemaRegistry.Serdes.Avro/SpecificDeserializerImpl.cs b/src/Confluent.SchemaRegistry.Serdes.Avro/SpecificDeserializerImpl.cs
index f15e7b2e6..5d25eceed 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Avro/SpecificDeserializerImpl.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Avro/SpecificDeserializerImpl.cs
@@ -126,7 +126,7 @@ public async Task Deserialize(string topic, Headers headers, byte[] array, bo
}
string subject = GetSubjectName(topic, isKey, null);
- Schema latestSchema = null;
+ RegisteredSchema latestSchema = null;
if (subject != null)
{
latestSchema = await GetReaderSchema(subject)
diff --git a/src/Confluent.SchemaRegistry.Serdes.Json/JsonDeserializer.cs b/src/Confluent.SchemaRegistry.Serdes.Json/JsonDeserializer.cs
index 21f0d7a8c..9d75e708d 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Json/JsonDeserializer.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Json/JsonDeserializer.cs
@@ -63,6 +63,8 @@ public class JsonDeserializer : AsyncDeserializer where T : cl
private JsonSchema schema = null;
+ private bool validate = true;
+
private JsonSerializerSettings jsonSchemaGeneratorSettingsSerializerSettings {
get =>
#if NET8_0_OR_GREATER
@@ -110,6 +112,7 @@ public JsonDeserializer(ISchemaRegistryClient schemaRegistryClient, JsonDeserial
if (config.UseLatestVersion != null) { this.useLatestVersion = config.UseLatestVersion.Value; }
if (config.UseLatestWithMetadata != null) { this.useLatestWithMetadata = config.UseLatestWithMetadata; }
if (config.SubjectNameStrategy != null) { this.subjectNameStrategy = config.SubjectNameStrategy.Value.ToDelegate(); }
+ if (config.Validate!= null) { this.validate= config.Validate.Value; }
}
///
@@ -172,7 +175,7 @@ public override async Task DeserializeAsync(ReadOnlyMemory data, bool i
bool isKey = context.Component == MessageComponentType.Key;
string topic = context.Topic;
string subject = GetSubjectName(topic, isKey, null);
- Schema latestSchema = null;
+ RegisteredSchema latestSchema = null;
if (subject != null)
{
latestSchema = await GetReaderSchema(subject)
@@ -228,10 +231,9 @@ public override async Task DeserializeAsync(ReadOnlyMemory data, bool i
.ContinueWith(t => (JToken)t.Result)
.ConfigureAwait(continueOnCapturedContext: false);
- if (schema != null)
+ if (schema != null && validate)
{
var validationResult = validator.Validate(json, schema);
-
if (validationResult.Count > 0)
{
throw new InvalidDataException("Schema validation failed for properties: [" +
@@ -249,7 +251,7 @@ public override async Task DeserializeAsync(ReadOnlyMemory data, bool i
{
string serializedString = jsonReader.ReadToEnd();
- if (schema != null)
+ if (schema != null && validate)
{
var validationResult = validator.Validate(serializedString, schema);
diff --git a/src/Confluent.SchemaRegistry.Serdes.Json/JsonDeserializerConfig.cs b/src/Confluent.SchemaRegistry.Serdes.Json/JsonDeserializerConfig.cs
index 26361dcef..9fd8baf7b 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Json/JsonDeserializerConfig.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Json/JsonDeserializerConfig.cs
@@ -53,6 +53,13 @@ public static class PropertyNames
/// Possible values:
///
public const string SubjectNameStrategy = "json.deserializer.subject.name.strategy";
+
+ ///
+ /// Specifies whether to validate payloads against the schema.
+ ///
+ /// default: true
+ ///
+ public const string Validate= "json.serializer.validate";
}
@@ -124,5 +131,18 @@ public SubjectNameStrategy? SubjectNameStrategy
else { this.properties[PropertyNames.SubjectNameStrategy] = value.ToString(); }
}
}
+
+
+ ///
+ /// Specifies whether or not the JSON serializer should attempt to
+ /// validate the payload against the schema.
+ ///
+ /// default: true
+ ///
+ public bool? Validate
+ {
+ get { return GetBool(PropertyNames.Validate); }
+ set { SetObject(PropertyNames.Validate, value); }
+ }
}
}
diff --git a/src/Confluent.SchemaRegistry.Serdes.Json/JsonSerializer.cs b/src/Confluent.SchemaRegistry.Serdes.Json/JsonSerializer.cs
index 643c0f207..229af15fe 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Json/JsonSerializer.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Json/JsonSerializer.cs
@@ -75,6 +75,8 @@ public class JsonSerializer : AsyncSerializer where T : class
private string schemaText;
private string schemaFullname;
+ private bool validate = true;
+
private JsonSerializerSettings jsonSchemaGeneratorSettingsSerializerSettings {
get =>
#if NET8_0_OR_GREATER
@@ -129,6 +131,7 @@ public JsonSerializer(ISchemaRegistryClient schemaRegistryClient, JsonSerializer
if (config.LatestCompatibilityStrict != null) { this.latestCompatibilityStrict = config.LatestCompatibilityStrict.Value; }
if (config.UseLatestWithMetadata != null) { this.useLatestWithMetadata = config.UseLatestWithMetadata; }
if (config.SubjectNameStrategy != null) { this.subjectNameStrategy = config.SubjectNameStrategy.Value.ToDelegate(); }
+ if (config.Validate!= null) { this.validate= config.Validate.Value; }
if (this.useLatestVersion && this.autoRegisterSchema)
{
@@ -245,10 +248,13 @@ public override async Task SerializeAsync(T value, SerializationContext
}
var serializedString = Newtonsoft.Json.JsonConvert.SerializeObject(value, jsonSchemaGeneratorSettingsSerializerSettings);
- var validationResult = validator.Validate(serializedString, this.schema);
- if (validationResult.Count > 0)
+ if (validate)
{
- throw new InvalidDataException("Schema validation failed for properties: [" + string.Join(", ", validationResult.Select(r => r.Path)) + "]");
+ var validationResult = validator.Validate(serializedString, this.schema);
+ if (validationResult.Count > 0)
+ {
+ throw new InvalidDataException("Schema validation failed for properties: [" + string.Join(", ", validationResult.Select(r => r.Path)) + "]");
+ }
}
using (var stream = new MemoryStream(initialBufferSize))
diff --git a/src/Confluent.SchemaRegistry.Serdes.Json/JsonSerializerConfig.cs b/src/Confluent.SchemaRegistry.Serdes.Json/JsonSerializerConfig.cs
index 8886299fc..13af2dea3 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Json/JsonSerializerConfig.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Json/JsonSerializerConfig.cs
@@ -92,6 +92,13 @@ public static class PropertyNames
/// Possible values:
///
public const string SubjectNameStrategy = "json.serializer.subject.name.strategy";
+
+ ///
+ /// Specifies whether to validate payloads against the schema.
+ ///
+ /// default: true
+ ///
+ public const string Validate= "json.serializer.validate";
}
@@ -220,5 +227,17 @@ public SubjectNameStrategy? SubjectNameStrategy
}
}
+
+ ///
+ /// Specifies whether or not the JSON serializer should attempt to
+ /// validate the payload against the schema.
+ ///
+ /// default: true
+ ///
+ public bool? Validate
+ {
+ get { return GetBool(PropertyNames.Validate); }
+ set { SetObject(PropertyNames.Validate, value); }
+ }
}
}
diff --git a/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufDeserializer.cs b/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufDeserializer.cs
index b58fc7817..3ff582484 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufDeserializer.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufDeserializer.cs
@@ -128,7 +128,7 @@ public override async Task DeserializeAsync(ReadOnlyMemory data, bool i
// Currently Protobuf does not support migration rules because of lack of support for DynamicMessage
// See https://github.com/protocolbuffers/protobuf/issues/658
/*
- Schema latestSchema = await SerdeUtils.GetReaderSchema(schemaRegistryClient, subject, useLatestWithMetadata, useLatestVersion)
+ RegisteredSchema latestSchema = await SerdeUtils.GetReaderSchema(schemaRegistryClient, subject, useLatestWithMetadata, useLatestVersion)
.ConfigureAwait(continueOnCapturedContext: false);
*/
diff --git a/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufSerializer.cs b/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufSerializer.cs
index 898ec21da..53ad08c4d 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufSerializer.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufSerializer.cs
@@ -54,7 +54,7 @@ namespace Confluent.SchemaRegistry.Serdes
///
public class ProtobufSerializer : AsyncSerializer where T : IMessage, new()
{
- private bool skipKnownTypes;
+ private bool skipKnownTypes = true;
private bool useDeprecatedFormat;
private ReferenceSubjectNameStrategyDelegate referenceSubjectNameStrategy;
@@ -190,7 +190,7 @@ private async Task> RegisterOrGetReferences(FileDescriptor
for (int i=0; i ParseSchema(Schema schema)
.ConfigureAwait(continueOnCapturedContext: false);
return ProtobufUtils.Parse(schema.SchemaString, references);
}
+
+ protected override bool IgnoreReference(string name)
+ {
+ return name.StartsWith("confluent/") ||
+ name.StartsWith("google/protobuf/") ||
+ name.StartsWith("google/type/");
+ }
}
}
diff --git a/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufSerializerConfig.cs b/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufSerializerConfig.cs
index b40a0d9ca..72071509b 100644
--- a/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufSerializerConfig.cs
+++ b/src/Confluent.SchemaRegistry.Serdes.Protobuf/ProtobufSerializerConfig.cs
@@ -195,7 +195,7 @@ public IDictionary UseLatestWithMetadata
/// Specifies whether or not the Protobuf serializer should skip known types
/// when resolving dependencies.
///
- /// default: false
+ /// default: true
///
public bool? SkipKnownTypes
{
diff --git a/src/Confluent.SchemaRegistry/AsyncSerde.cs b/src/Confluent.SchemaRegistry/AsyncSerde.cs
index 082f2dfd9..698e6873b 100644
--- a/src/Confluent.SchemaRegistry/AsyncSerde.cs
+++ b/src/Confluent.SchemaRegistry/AsyncSerde.cs
@@ -18,6 +18,7 @@
#pragma warning disable CS0618
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -41,7 +42,7 @@ public abstract class AsyncSerde
protected SemaphoreSlim serdeMutex = new SemaphoreSlim(1);
- private readonly IDictionary parsedSchemaCache = new Dictionary();
+ private readonly IDictionary parsedSchemaCache = new ConcurrentDictionary();
private SemaphoreSlim parsedSchemaMutex = new SemaphoreSlim(1);
protected AsyncSerde(ISchemaRegistryClient schemaRegistryClient, SerdeConfig config, RuleRegistry ruleRegistry = null)
@@ -59,7 +60,7 @@ protected AsyncSerde(ISchemaRegistryClient schemaRegistryClient, SerdeConfig con
foreach (IRuleExecutor executor in this.ruleRegistry.GetExecutors())
{
- executor.Configure(ruleConfigs);
+ executor.Configure(ruleConfigs, schemaRegistryClient);
}
}
@@ -98,10 +99,15 @@ protected string GetSubjectName(string topic, bool isKey, string recordType)
protected async Task GetParsedSchema(Schema schema)
{
+ if (parsedSchemaCache.TryGetValue(schema, out TParsedSchema parsedSchema))
+ {
+ return parsedSchema;
+ }
+
await parsedSchemaMutex.WaitAsync().ConfigureAwait(continueOnCapturedContext: false);
try
{
- if (!parsedSchemaCache.TryGetValue(schema, out TParsedSchema parsedSchema))
+ if (!parsedSchemaCache.TryGetValue(schema, out parsedSchema))
{
if (parsedSchemaCache.Count > schemaRegistryClient.MaxCachedSchemas)
{
@@ -136,6 +142,11 @@ protected async Task> ResolveReferences(Schema schem
.ConfigureAwait(continueOnCapturedContext: false);
return result;
}
+
+ protected virtual bool IgnoreReference(string name)
+ {
+ return false;
+ }
private async Task> ResolveReferences(
Schema schema, IDictionary schemas, ISet visited)
@@ -143,7 +154,7 @@ private async Task> ResolveReferences(
IList references = schema.References;
foreach (SchemaReference reference in references)
{
- if (visited.Contains(reference.Name))
+ if (IgnoreReference(reference.Name) || visited.Contains(reference.Name))
{
continue;
}
@@ -166,11 +177,14 @@ await ResolveReferences(s, schemas, visited)
return schemas;
}
- protected async Task> GetMigrations(string subject, Schema writerSchema, Schema readerSchema)
+ protected async Task> GetMigrations(string subject, Schema writer, RegisteredSchema readerSchema)
{
+ var writerSchema = await schemaRegistryClient.LookupSchemaAsync(subject, writer, false, false)
+ .ConfigureAwait(continueOnCapturedContext: false);
+
RuleMode migrationMode;
- Schema first;
- Schema last;
+ RegisteredSchema first;
+ RegisteredSchema last;
IList migrations = new List();
if (writerSchema.Version < readerSchema.Version)
{
@@ -217,7 +231,7 @@ protected async Task> GetMigrations(string subject, Schema writ
return migrations;
}
- private async Task> GetSchemasBetween(string subject, Schema first, Schema last)
+ private async Task> GetSchemasBetween(string subject, RegisteredSchema first, RegisteredSchema last)
{
if (last.Version - first.Version <= 1)
{
@@ -297,6 +311,7 @@ protected async Task