Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
af27c17
Disabling SSL option for development purposes to work with vnext emul…
philnach Nov 2, 2025
24546e5
Add ability to provide password with PFX
philnach Nov 2, 2025
bbd7a88
Merge branch 'main' into vnextemulator
philnach Nov 7, 2025
d3e5996
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosEx…
philnach Nov 9, 2025
cbd828a
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosEx…
philnach Nov 9, 2025
1752de4
Dispose properly of the X509Chain
philnach Nov 10, 2025
74671a2
Updates to log on failure
philnach Nov 10, 2025
d7a2c0c
Add logger to certificates as supposed to writeline
philnach Nov 23, 2025
51c5ac2
remove duplicate cert check
philnach Nov 23, 2025
c4c6a20
PR feedback
philnach Nov 24, 2025
c2b8dc8
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosEx…
philnach Nov 24, 2025
24205f3
add null check
philnach Nov 24, 2025
fbae2e8
add date check to cert
philnach Nov 24, 2025
d11b2e3
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosEx…
philnach Nov 24, 2025
1c98b72
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosEx…
philnach Nov 24, 2025
424c5f6
fix build issues
philnach Nov 24, 2025
55a108f
Merge branch 'vnextemulator' of https://github.com/philnach/data-migr…
philnach Nov 24, 2025
3c2af00
Update Extensions/Cosmos/CERTIFICATE_EXAMPLES.md
philnach Nov 24, 2025
e5965c3
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosSe…
philnach Nov 24, 2025
d4edcfd
Merge branch 'main' into vnextemulator
markjbrown Nov 24, 2025
c8170df
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosEx…
philnach Nov 25, 2025
a95d075
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosSe…
philnach Nov 25, 2025
d2cfecc
Remove all the complexity and simplify to just the disabledsetting
philnach Nov 25, 2025
52a5977
Merge branch 'vnextemulator' of https://github.com/philnach/data-migr…
philnach Nov 25, 2025
7387b3a
missed merge
philnach Nov 25, 2025
8469486
remove examples since we don't support any of them
philnach Nov 25, 2025
a1cc6d4
remove vnext mention
philnach Nov 25, 2025
f0a53e7
simplify
philnach Nov 26, 2025
ccc479f
slight fix on readme
philnach Nov 26, 2025
e9b085a
fix last thing copilot was complaining about
philnach Nov 26, 2025
8709b82
Update Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosEx…
philnach Nov 27, 2025
47a50c9
Add AllowBulkExecution
philnach Nov 29, 2025
ca8f2f0
Minor wording tweaks
philnach Nov 29, 2025
5169381
Add a test for the SSL disable
philnach Nov 29, 2025
a654f9d
Fix broken tests
philnach Nov 29, 2025
9dd7437
Merge branch 'main' into vnextemulator
philnach Dec 5, 2025
5fddf94
Merge branch 'main' into vnextemulator
philnach Dec 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Logging;
using Moq;

namespace Cosmos.DataTransfer.CosmosExtension.UnitTests
{
[TestClass]
public class CertificateConfigurationTests
{
[TestMethod]
public void CosmosSettingsBase_WithDisableSslValidation_ShouldValidateSuccessfully()
{
var settings = new TestableCosmosSettings
{
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDj...",
Database = "testDb",
Container = "testContainer",
DisableSslValidation = true
};

var validationResults = settings.Validate(new ValidationContext(settings)).ToList();

Assert.AreEqual(0, validationResults.Count, "Should have no validation errors");
}

[TestMethod]
public void CosmosSettingsBase_WithDisableSslValidationFalse_ShouldUseDefaultValidation()
{
var settings = new TestableCosmosSettings
{
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDj...",
Database = "testDb",
Container = "testContainer",
DisableSslValidation = false
};

var validationResults = settings.Validate(new ValidationContext(settings)).ToList();

Assert.AreEqual(0, validationResults.Count, "Settings should validate with DisableSslValidation=false");
}

[TestMethod]
public void CreateClient_WithDisableSslValidation_LogsWarningAndSetsCallback()
{
var loggerMock = new Mock<ILogger>();
var settings = new TestableCosmosSettings
{
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
Database = "testDb",
Container = "testContainer",
DisableSslValidation = true
};

var client = CosmosExtensionServices.CreateClient(settings, "TestDisplay", loggerMock.Object);

// Verify warning was logged when DisableSslValidation is true
loggerMock.Verify(
x => x.Log(
LogLevel.Warning,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("SSL certificate validation is DISABLED")),
It.IsAny<Exception?>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
Times.Once);

// Verify client was created successfully
Assert.IsNotNull(client, "CosmosClient should be created");
}

[TestMethod]
public void CreateClient_WithoutDisableSslValidation_DoesNotLogWarning()
{
var loggerMock = new Mock<ILogger>();
var settings = new TestableCosmosSettings
{
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
Database = "testDb",
Container = "testContainer",
DisableSslValidation = false
};

var client = CosmosExtensionServices.CreateClient(settings, "TestDisplay", loggerMock.Object);

// Verify warning was NOT logged when DisableSslValidation is false
loggerMock.Verify(
x => x.Log(
LogLevel.Warning,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("SSL certificate validation is DISABLED")),
It.IsAny<Exception?>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()),
Times.Never);

// Verify client was created successfully
Assert.IsNotNull(client, "CosmosClient should be created");
}

private class TestableCosmosSettings : CosmosSettingsBase
{
}
}
}
Comment thread
philnach marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Cosmos.DataTransfer.CosmosExtension;

namespace Cosmos.DataTransfer.CosmosExtension.UnitTests
{
[TestClass]
public class CosmosSettingsBaseTests
{
private class TestableCosmosSettings : CosmosSettingsBase { }

[TestMethod]
public void AllowBulkExecution_Property_ShouldSetAndGet()
{
var settings = new TestableCosmosSettings
{
AllowBulkExecution = true
};

Assert.IsTrue(settings.AllowBulkExecution, "AllowBulkExecution should be true when set to true");

settings.AllowBulkExecution = false;
Assert.IsFalse(settings.AllowBulkExecution, "AllowBulkExecution should be false when set to false");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ public async Task WriteAsync(IAsyncEnumerable<IDataItem> dataItems, IConfigurati
var settings = config.Get<CosmosSinkSettings>();
settings.Validate();

var client = CosmosExtensionServices.CreateClient(settings!, DisplayName, dataSource.DisplayName);
var client = CosmosExtensionServices.CreateClient(settings!, DisplayName, logger, dataSource.DisplayName);

Container container;
if (settings!.UseRbacAuth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async IAsyncEnumerable<IDataItem> ReadAsync(IConfiguration config, ILogge
var settings = config.Get<CosmosSourceSettings>();
settings.Validate();

var client = CosmosExtensionServices.CreateClient(settings!, DisplayName);
var client = CosmosExtensionServices.CreateClient(settings!, DisplayName, logger);

Container container;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
using Azure.Identity;
using Cosmos.DataTransfer.Interfaces;
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Keys.Cryptography;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Encryption;
using Microsoft.Extensions.Logging;
using System.Globalization;
using System.Reflection;
using Azure.Core;
using System.Text.RegularExpressions;
using Microsoft.Azure.Cosmos.Encryption;
using Azure.Security.KeyVault.Keys.Cryptography;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;

namespace Cosmos.DataTransfer.CosmosExtension
{
Expand Down Expand Up @@ -37,7 +36,7 @@ public static class CosmosExtensionServices
return new HttpClient(handler);
});

public static CosmosClient CreateClient(CosmosSettingsBase settings, string displayName, string? sourceDisplayName = null)
public static CosmosClient CreateClient(CosmosSettingsBase settings, string displayName, ILogger logger, string? sourceDisplayName = null)
{
string userAgentString = CreateUserAgentString(displayName, sourceDisplayName);

Expand Down Expand Up @@ -76,6 +75,13 @@ public static CosmosClient CreateClient(CosmosSettingsBase settings, string disp
? () => _httpClientWithDefaultCredentialsAndPreAuth.Value
: () => _httpClientWithDefaultCredentials.Value;
}

// Disable SSL certificate validation for development scenarios
if (settings.DisableSslValidation)
{
logger.LogWarning("SSL certificate validation is DISABLED. This should ONLY be used for development scenarios. Never use in production.");
clientOptions.ServerCertificateCustomValidationCallback = (cert, chain, errors) => true;
}

CosmosClient? cosmosClient;
if (settings.UseRbacAuth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,26 @@ public abstract class CosmosSettingsBase : IValidatableObject
/// </summary>
public bool LimitToEndpoint { get; set; } = false;

/// <summary>
/// Disables SSL certificate validation for the Cosmos DB connection.
/// This is intended for use with local development environments.
/// WARNING: Never use this option in production as it disables critical security checks.
/// </summary>
public bool DisableSslValidation { get; set; } = false;

/// <summary>
/// Enables bulk execution for Cosmos DB operations.
/// When set to <c>true</c>, operations such as bulk inserts and updates are optimized for performance.
/// <para>
/// <b>Default:</b> <c>false</c>
/// </para>
/// <para>
/// <b>Warning:</b> Use with caution, as enabling bulk execution may affect consistency and error handling.
/// Review Cosmos DB documentation for bulk operation caveats before enabling in production environments.
/// </para>
/// </summary>
public bool AllowBulkExecution { get; set; } = false;

public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!UseRbacAuth && string.IsNullOrEmpty(ConnectionString))
Expand Down
64 changes: 61 additions & 3 deletions Extensions/Cosmos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,44 @@ The Cosmos data transfer extension provides source and sink capabilities for rea

## Settings


### Main Settings

| Setting | Description | Default |
|------------------------|---------------------------------------------------------------------------------------------------|-----------|
| ConnectionString | Cosmos DB connection string (AccountEndpoint + AccountKey) | |
| UseRbacAuth | Use Role Based Access Control for authentication | false |
| AccountEndpoint | Cosmos DB account endpoint (required for RBAC) | |
| EnableInteractiveCredentials | Prompt for Azure login if default credentials are unavailable | false |
| Database | Cosmos DB database name | |
| Container | Cosmos DB container name | |
| WebProxy | Proxy server URL for Cosmos DB connections | |
| InitClientEncryption | Enable Always Encrypted feature | false |
| LimitToEndpoint | Restrict client to endpoint (see CosmosClientOptions.LimitToEndpoint) | false |
| DisableSslValidation | Disable SSL certificate validation (for local dev only; not for production) | false |
| AllowBulkExecution | Enable bulk execution for optimized performance. <br>**Warning:** May affect consistency and error handling. | false |

Source and sink require settings used to locate and access the Cosmos DB account. This can be done in one of two ways:

- Using a `ConnectionString` that includes an AccountEndpoint and AccountKey
- Using RBAC (Role Based Access Control) by setting `UseRbacAuth` to true and specifying `AccountEndpoint` and optionally `EnableInteractiveCredentials` to prompt the user to log in to Azure if default credentials are not available. See ([migrate-passwordless](https://learn.microsoft.com/azure/cosmos-db/nosql/migrate-passwordless?tabs=sign-in-azure-cli%2Cdotnet%2Cazure-portal-create%2Cazure-portal-associate%2Capp-service-identity) for how to configure Cosmos DB for passwordless access.


### Bulk Execution

The extension supports bulk execution for Cosmos DB operations. When the `AllowBulkExecution` setting is set to `true`, operations such as bulk inserts and updates are optimized for performance. Use with caution, as bulk execution may affect consistency and error handling. Default is `false`.

Example:

```json
{
"ConnectionString": "AccountEndpoint=https://...",
"Database": "myDb",
"Container": "myContainer",
"AllowBulkExecution": true
}
```

Source and sink settings also both require parameters to specify the data location within a Cosmos DB account:

- `Database`
Expand All @@ -27,7 +60,7 @@ Source supports the following optional parameters:

### Always Encrypted

Source and Sink support Always Encrypted as an optional parameter. When `InitClientEncryption` is set to `true`, the extension will initialize the Cosmos client with the Always Encrypted feature enabled. This allows for the use of encrypted fields in the Cosmos DB container. The extension will automatically decrypt the fields when reading from the source and encrypt the fields when writing to the sink.
Source and Sink support Always Encrypted as an optional parameter. When `InitClientEncryption` is set to `true`, the extension will initialize the Cosmos client with the Always Encrypted feature enabled. This allows for the use of encrypted fields in the Cosmos DB container. The extension will automatically decrypt the fields when reading from the source and encrypt the fields when writing to the sink.
</br>
The extension will also automatically handle the encryption keys and encryption policy for the client, but it requires `UseRbacAuth` to be set to `true` and the user to have the necessary permissions to access the key vault.
</br>
Expand Down Expand Up @@ -70,22 +103,39 @@ Or with RBAC:
}
```

#### Disable SSL Validation Configuration Example

For development purposes with SSL validation disabled:

```json
{
"ConnectionString": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDj...",
"Database":"myDb",
"Container":"myContainer",
Comment thread
philnach marked this conversation as resolved.
Resolved
"DisableSslValidation": true
}
```

### Sink Settings

#### **Partition Key Settings**

- **`PartitionKeyPath`**: Specifies the partition key path when creating the container (e.g., `/id`) if it does not exist.
- **`PartitionKeyPaths`**: Use this to supply an array of up to 3 paths for hierarchical partition keys.

#### **Database Management**

- **`UseAutoscaleForDatabase`**: Specifies if the database will be created with autoscale enabled or manual. Defaults to `false`. manual.

#### **Container Management**

- **`RecreateContainer`**: Optional, defaults to `false`. Deletes and recreates the container to ensure only newly imported data is present.
- **`CreatedContainerMaxThroughput`**: Specifies the initial throughput (in RUs) for a newly created container.
- **`UseAutoscaleForCreatedContainer`**: Enables autoscale for the newly created container.
- **`UseSharedThroughput`**: Set to `true` to use shared throughput provisioned at the database level.

#### **Batching and Write Behavior**

- **`BatchSize`**: Optional, defaults to `100`. Sets the number of items to accumulate before inserting.
- **`WriteMode`**: Specifies the type of data write to use. Options:
- `InsertStream`
Expand All @@ -94,6 +144,7 @@ Or with RBAC:
- `Upsert`

#### **Connection Settings**

- **`ConnectionMode`**: Controls how the client connects to the Cosmos DB service. Options:
- `Gateway` (default)
- `Direct`
Expand All @@ -104,16 +155,23 @@ Or with RBAC:
- **`PreAuthenticate`**: Optional, defaults to `false`. When `true`, enables pre-authentication on the HttpClient, which sends credentials with the initial request rather than waiting for a 401/407 challenge. This can save extra round-trips but should only be used when the endpoint is trusted.

- **`LimitToEndpoint`**: Optional, defaults to `false`. When the value of this property is false, the Cosmos DB SDK will automatically discover
write and read regions, and use them when the configured application region is not available.
write and read regions, and use them when the configured application region is not available.
When set to `true`, availability is limited to the endpoint specified.
- **Note**: [CosmosClientOptions.LimitToEndpoint Property](https://learn.microsoft.com/dotnet/api/microsoft.azure.cosmos.cosmosclientoptions.limittoendpoint?view=azure-dotnet). When using the Cosmos DB Emulator Container for Linux it's been observed
setting the value to `true` enables import and export of data.
setting the value to `true` enables import and export of data.

#### **SSL/Certificate Settings**

- **`DisableSslValidation`**: Optional, defaults to `false`. Disables SSL certificate validation for development/emulator scenarios.
- **⚠️ WARNING**: Only use this for development purposes. Never use in production environments as it disables critical security checks and makes connections vulnerable to man-in-the-middle attacks.

#### **Serverless Account**

- **`IsServerlessAccount`**: Specifies whether the target account uses Serverless instead of Provisioned throughput, which affects the way containers are created.
- **Note**: Serverless accounts cannot have shared throughput. See [Azure Cosmos DB serverless account type](https://learn.microsoft.com/azure/cosmos-db/serverless#use-serverless-resources).

#### **Client Behavior**

- **`PreserveMixedCaseIds`**: Optional, defaults to `false`. Writes `id` fields with their original casing while generating a separate lowercased `id` field as required by Cosmos.
- **`IgnoreNullValues`**: Optional. Excludes fields with null values when writing to Cosmos DB.
- **`InitClientEncryption`**: Optional, defaults to `false`. Uses client-side encryption with the container. Can only be used with `UseRbacAuth` set to `true`
Expand Down
Loading