Skip to content

Commit 37cd7ef

Browse files
authored
Merge pull request #220 from AzureCosmosDB/copilot/support-authenticated-proxies
Support authenticated proxies with separate settings for WebProxy, HttpClient credentials, and PreAuthentication
2 parents 525aa84 + 36c521f commit 37cd7ef

6 files changed

Lines changed: 248 additions & 5 deletions

File tree

ExampleConfigs.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,26 @@
202202
]
203203
}
204204
```
205+
206+
## JSON to Cosmos-NoSQL (Using Authenticated Proxy)
207+
208+
```json
209+
{
210+
"Source": "json",
211+
"Sink": "cosmos-nosql",
212+
"SourceSettings": {
213+
"FilePath": "c:\\data\\sales-data.json"
214+
},
215+
"SinkSettings": {
216+
"ConnectionString": "AccountEndpoint=https://...",
217+
"Database": "myDb",
218+
"Container": "myContainer",
219+
"PartitionKeyPath": "/id",
220+
"WriteMode": "Insert",
221+
"WebProxy": "http://yourproxy.server.com/",
222+
"UseDefaultProxyCredentials": true,
223+
"UseDefaultCredentials": true,
224+
"PreAuthenticate": true
225+
}
226+
}
227+
```

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension.UnitTests/CosmosSinkSettingsTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,85 @@ public void GetValidationErrors_WhenRbacWithoutInitClientEncryption_Succeeds()
209209

210210
Assert.AreEqual(0, validationErrors.Count());
211211
}
212+
213+
[TestMethod]
214+
public void UseDefaultProxyCredentials_DefaultsToFalse()
215+
{
216+
var settings = new CosmosSinkSettings
217+
{
218+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
219+
Database = "db",
220+
Container = "container",
221+
};
222+
223+
Assert.IsFalse(settings.UseDefaultProxyCredentials);
224+
}
225+
226+
[TestMethod]
227+
public void UseDefaultProxyCredentials_CanBeSetToTrue()
228+
{
229+
var settings = new CosmosSinkSettings
230+
{
231+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
232+
Database = "db",
233+
Container = "container",
234+
UseDefaultProxyCredentials = true,
235+
};
236+
237+
Assert.IsTrue(settings.UseDefaultProxyCredentials);
238+
}
239+
240+
[TestMethod]
241+
public void UseDefaultCredentials_DefaultsToFalse()
242+
{
243+
var settings = new CosmosSinkSettings
244+
{
245+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
246+
Database = "db",
247+
Container = "container",
248+
};
249+
250+
Assert.IsFalse(settings.UseDefaultCredentials);
251+
}
252+
253+
[TestMethod]
254+
public void UseDefaultCredentials_CanBeSetToTrue()
255+
{
256+
var settings = new CosmosSinkSettings
257+
{
258+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
259+
Database = "db",
260+
Container = "container",
261+
UseDefaultCredentials = true,
262+
};
263+
264+
Assert.IsTrue(settings.UseDefaultCredentials);
265+
}
266+
267+
[TestMethod]
268+
public void PreAuthenticate_DefaultsToFalse()
269+
{
270+
var settings = new CosmosSinkSettings
271+
{
272+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
273+
Database = "db",
274+
Container = "container",
275+
};
276+
277+
Assert.IsFalse(settings.PreAuthenticate);
278+
}
279+
280+
[TestMethod]
281+
public void PreAuthenticate_CanBeSetToTrue()
282+
{
283+
var settings = new CosmosSinkSettings
284+
{
285+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
286+
Database = "db",
287+
Container = "container",
288+
PreAuthenticate = true,
289+
};
290+
291+
Assert.IsTrue(settings.PreAuthenticate);
292+
}
212293
}

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension.UnitTests/CosmosSourceSettingsTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,85 @@ public void Validate_WithAccountEndpoint_Succeeds()
6060

6161
settings.Validate();
6262
}
63+
64+
[TestMethod]
65+
public void UseDefaultProxyCredentials_DefaultsToFalse()
66+
{
67+
var settings = new CosmosSourceSettings
68+
{
69+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
70+
Database = "db",
71+
Container = "container",
72+
};
73+
74+
Assert.IsFalse(settings.UseDefaultProxyCredentials);
75+
}
76+
77+
[TestMethod]
78+
public void UseDefaultProxyCredentials_CanBeSetToTrue()
79+
{
80+
var settings = new CosmosSourceSettings
81+
{
82+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
83+
Database = "db",
84+
Container = "container",
85+
UseDefaultProxyCredentials = true,
86+
};
87+
88+
Assert.IsTrue(settings.UseDefaultProxyCredentials);
89+
}
90+
91+
[TestMethod]
92+
public void UseDefaultCredentials_DefaultsToFalse()
93+
{
94+
var settings = new CosmosSourceSettings
95+
{
96+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
97+
Database = "db",
98+
Container = "container",
99+
};
100+
101+
Assert.IsFalse(settings.UseDefaultCredentials);
102+
}
103+
104+
[TestMethod]
105+
public void UseDefaultCredentials_CanBeSetToTrue()
106+
{
107+
var settings = new CosmosSourceSettings
108+
{
109+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
110+
Database = "db",
111+
Container = "container",
112+
UseDefaultCredentials = true,
113+
};
114+
115+
Assert.IsTrue(settings.UseDefaultCredentials);
116+
}
117+
118+
[TestMethod]
119+
public void PreAuthenticate_DefaultsToFalse()
120+
{
121+
var settings = new CosmosSourceSettings
122+
{
123+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
124+
Database = "db",
125+
Container = "container",
126+
};
127+
128+
Assert.IsFalse(settings.PreAuthenticate);
129+
}
130+
131+
[TestMethod]
132+
public void PreAuthenticate_CanBeSetToTrue()
133+
{
134+
var settings = new CosmosSourceSettings
135+
{
136+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
137+
Database = "db",
138+
Container = "container",
139+
PreAuthenticate = true,
140+
};
141+
142+
Assert.IsTrue(settings.PreAuthenticate);
143+
}
63144
}

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosExtensionServices.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,34 @@
99
using Microsoft.Azure.Cosmos.Encryption;
1010
using Azure.Security.KeyVault.Keys.Cryptography;
1111
using System.Net;
12+
using System.Net.Http;
1213

1314
namespace Cosmos.DataTransfer.CosmosExtension
1415
{
1516
public static class CosmosExtensionServices
1617
{
18+
// Static HttpClient instances with different configurations for reuse across connections
19+
// This avoids connection exhaustion and properly handles credentials
20+
private static readonly Lazy<HttpClient> _httpClientWithDefaultCredentials = new Lazy<HttpClient>(() =>
21+
{
22+
var handler = new HttpClientHandler
23+
{
24+
Credentials = CredentialCache.DefaultNetworkCredentials,
25+
PreAuthenticate = false
26+
};
27+
return new HttpClient(handler);
28+
});
29+
30+
private static readonly Lazy<HttpClient> _httpClientWithDefaultCredentialsAndPreAuth = new Lazy<HttpClient>(() =>
31+
{
32+
var handler = new HttpClientHandler
33+
{
34+
Credentials = CredentialCache.DefaultNetworkCredentials,
35+
PreAuthenticate = true
36+
};
37+
return new HttpClient(handler);
38+
});
39+
1740
public static CosmosClient CreateClient(CosmosSettingsBase settings, string displayName, string? sourceDisplayName = null)
1841
{
1942
string userAgentString = CreateUserAgentString(displayName, sourceDisplayName);
@@ -37,7 +60,21 @@ public static CosmosClient CreateClient(CosmosSettingsBase settings, string disp
3760
};
3861

3962
if (!string.IsNullOrEmpty(settings.WebProxy)){
40-
clientOptions.WebProxy = new WebProxy(settings.WebProxy);
63+
var webProxy = new WebProxy(settings.WebProxy);
64+
if (settings.UseDefaultProxyCredentials)
65+
{
66+
webProxy.UseDefaultCredentials = true;
67+
}
68+
clientOptions.WebProxy = webProxy;
69+
}
70+
71+
// Configure the HttpClient with default credentials if requested
72+
// This enables authenticated proxy support for the underlying HTTP connections
73+
if (settings.UseDefaultCredentials)
74+
{
75+
clientOptions.HttpClientFactory = settings.PreAuthenticate
76+
? () => _httpClientWithDefaultCredentialsAndPreAuth.Value
77+
: () => _httpClientWithDefaultCredentials.Value;
4178
}
4279

4380
CosmosClient? cosmosClient;

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosSettingsBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public abstract class CosmosSettingsBase : IValidatableObject
1212
public string? Container { get; set; }
1313
public ConnectionMode ConnectionMode { get; set; } = ConnectionMode.Gateway;
1414
public string? WebProxy { get; set; }
15+
public bool UseDefaultProxyCredentials { get; set; } = false;
16+
public bool UseDefaultCredentials { get; set; } = false;
17+
public bool PreAuthenticate { get; set; } = false;
1518
public bool UseRbacAuth { get; set; }
1619
public string? AccountEndpoint { get; set; }
1720
public bool EnableInteractiveCredentials { get; set; }

Extensions/Cosmos/README.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ Source and sink settings also both require parameters to specify the data locati
1616
- `Database`
1717
- `Container`
1818

19-
Source supports an optional `IncludeMetadataFields` parameter (`false` by default) to enable inclusion of built-in Cosmos fields prefixed with `"_"`, for example `"_etag"` and `"_ts"`. An optional PartitionKeyValue setting allows for filtering to a single partition. The optional Query setting allows further filtering using a Cosmos SQL statement. An optional `WebProxy` parameter (`null` by default) enables connections through a proxy.
19+
Source supports the following optional parameters:
20+
- `IncludeMetadataFields` (`false` by default) - Enables inclusion of built-in Cosmos fields prefixed with `"_"`, for example `"_etag"` and `"_ts"`.
21+
- `PartitionKeyValue` - Allows for filtering to a single partition.
22+
- `Query` - Allows further filtering using a Cosmos SQL statement.
23+
- `WebProxy` (`null` by default) - Enables connections through a proxy.
24+
- `UseDefaultProxyCredentials` (`false` by default) - When `true`, includes default credentials in the WebProxy request. Use this when connecting through an authenticated proxy that returns [`407 Proxy Authentication Required`](https://learn.microsoft.com/dotnet/api/system.net.webproxy.credentials?view=net-10.0#remarks).
25+
- `UseDefaultCredentials` (`false` by default) - When `true`, configures the underlying HttpClient with default network credentials. Use this when the connection to CosmosDB requires authentication through a proxy.
26+
- `PreAuthenticate` (`false` by default) - 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.
2027

2128
### Always Encrypted
2229

@@ -36,7 +43,10 @@ The extension will also automatically handle the encryption keys and encryption
3643
"IncludeMetadataFields": false,
3744
"PartitionKeyValue":"123",
3845
"Query":"SELECT * FROM c WHERE c.category='event'",
39-
"WebProxy":"http://yourproxy.server.com/"
46+
"WebProxy":"http://yourproxy.server.com/",
47+
"UseDefaultProxyCredentials": true,
48+
"UseDefaultCredentials": true,
49+
"PreAuthenticate": true
4050
}
4151
```
4252

@@ -52,8 +62,11 @@ Or with RBAC:
5262
"IncludeMetadataFields": false,
5363
"PartitionKeyValue":"123",
5464
"Query":"SELECT * FROM c WHERE c.category='event'",
55-
"InitClientEncryption": false
56-
"WebProxy":"http://yourproxy.server.com/"
65+
"InitClientEncryption": false,
66+
"WebProxy":"http://yourproxy.server.com/",
67+
"UseDefaultProxyCredentials": true,
68+
"UseDefaultCredentials": true,
69+
"PreAuthenticate": true
5770
}
5871
```
5972

@@ -85,6 +98,11 @@ Or with RBAC:
8598
- `Gateway` (default)
8699
- `Direct`
87100

101+
- **`WebProxy`**: Optional. Specifies the proxy server URL to use for connections (e.g., `http://yourproxy.server.com/`).
102+
- **`UseDefaultProxyCredentials`**: Optional, defaults to `false`. When `true`, includes default credentials in the WebProxy request. Use this when connecting through an authenticated proxy that returns [`407 Proxy Authentication Required`](https://learn.microsoft.com/dotnet/api/system.net.webproxy.credentials?view=net-10.0#remarks).
103+
- **`UseDefaultCredentials`**: Optional, defaults to `false`. When `true`, configures the underlying HttpClient with default network credentials. Use this when the connection to CosmosDB requires authentication through a proxy.
104+
- **`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.
105+
88106
- **`LimitToEndpoint`**: Optional, defaults to `false`. When the value of this property is false, the Cosmos DB SDK will automatically discover
89107
write and read regions, and use them when the configured application region is not available.
90108
When set to `true`, availability is limited to the endpoint specified.

0 commit comments

Comments
 (0)