Skip to content

Commit 6f41571

Browse files
authored
feat: add TLS configuration support to C# and Dart SDKs (#1137)
* feat: add TLS configuration support to Java SDK Signed-off-by: Mark Phelps <[email protected]> * chore: fmt java Signed-off-by: Mark Phelps <[email protected]> * chore: update java tests to use https Signed-off-by: Mark Phelps <[email protected]> * chore: update java test params for https Signed-off-by: Mark Phelps <[email protected]> * chore: add debug info for failing tests Signed-off-by: Mark Phelps <[email protected]> * fix: TlsConfig serialization compatibility with Rust FFI Change TlsConfig from Lombok @value to traditional class structure to match ClientOptions pattern and ensure proper JSON serialization Signed-off-by: Mark Phelps <[email protected]> * chore: fmt Signed-off-by: Mark Phelps <[email protected]> * chore: rm debug env vars in java tests Signed-off-by: Mark Phelps <[email protected]> * chore: mount tls file Signed-off-by: Mark Phelps <[email protected]> * chore: add default for fetcher builder Signed-off-by: Mark Phelps <[email protected]> * chore: check for file existence in builder Signed-off-by: Mark Phelps <[email protected]> * chore: add file existence checks for python too Signed-off-by: Mark Phelps <[email protected]> * chore: fmt Signed-off-by: Mark Phelps <[email protected]> * chore: default to 120 seconds on java builder Signed-off-by: Mark Phelps <[email protected]> * chore: only set request_timeout and update_interval in fetcher if > 0 Signed-off-by: Mark Phelps <[email protected]> * feat: add TLS configuration support to Ruby SDK and enable HTTPS testing - Add TlsConfig class with comprehensive TLS options matching Python/Java implementations - Support CA certificates, client certificates for mutual TLS, and insecure mode - Add convenience factory methods for common TLS scenarios - Update integration test framework to use HTTPS with TLS certificates for Ruby tests - Add comprehensive README documentation with TLS configuration examples Signed-off-by: Mark Phelps <[email protected]> * chore: rubocop fmt Signed-off-by: Mark Phelps <[email protected]> * feat: add TLS configuration support to C# and Dart SDKs and enable HTTPS testing Signed-off-by: Mark Phelps <[email protected]> * chore: dart json gen Signed-off-by: Mark Phelps <[email protected]> * chore: dart fmt Signed-off-by: Mark Phelps <[email protected]> * fix: add missing dart:io import for File class in Dart TLS config Signed-off-by: Mark Phelps <[email protected]> --------- Signed-off-by: Mark Phelps <[email protected]>
1 parent 9f9afd7 commit 6f41571

File tree

9 files changed

+641
-6
lines changed

9 files changed

+641
-6
lines changed

flipt-client-csharp/README.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,136 @@ The `FliptClient` supports the following authentication strategies:
123123
- [Client Token Authentication](https://docs.flipt.io/authentication/using-tokens)
124124
- [JWT Authentication](https://docs.flipt.io/authentication/using-jwts)
125125

126+
### TLS Configuration
127+
128+
The `FliptClient` supports configuring TLS settings for secure connections to Flipt servers. This is useful when:
129+
130+
- Connecting to Flipt servers with self-signed certificates
131+
- Using custom Certificate Authorities (CAs)
132+
- Implementing mutual TLS authentication
133+
- Testing with insecure connections (development only)
134+
135+
#### Basic TLS with Custom CA Certificate
136+
137+
```csharp
138+
using FliptClient;
139+
using FliptClient.Models;
140+
141+
// Using a CA certificate file
142+
var tlsConfig = TlsConfig.WithCaCertFile("/path/to/ca.pem");
143+
144+
var options = new ClientOptions
145+
{
146+
Url = "https://flipt.example.com",
147+
TlsConfig = tlsConfig,
148+
Authentication = new Authentication { ClientToken = "your-token" }
149+
};
150+
151+
using var client = new FliptClient(options);
152+
```
153+
154+
```csharp
155+
// Using CA certificate data directly
156+
var caCertData = File.ReadAllText("/path/to/ca.pem");
157+
var tlsConfig = TlsConfig.WithCaCertData(caCertData);
158+
159+
var options = new ClientOptions
160+
{
161+
Url = "https://flipt.example.com",
162+
TlsConfig = tlsConfig,
163+
Authentication = new Authentication { ClientToken = "your-token" }
164+
};
165+
166+
using var client = new FliptClient(options);
167+
```
168+
169+
#### Mutual TLS Authentication
170+
171+
```csharp
172+
// Using certificate and key files
173+
var tlsConfig = TlsConfig.WithMutualTls("/path/to/client.pem", "/path/to/client.key");
174+
175+
var options = new ClientOptions
176+
{
177+
Url = "https://flipt.example.com",
178+
TlsConfig = tlsConfig,
179+
Authentication = new Authentication { ClientToken = "your-token" }
180+
};
181+
182+
using var client = new FliptClient(options);
183+
```
184+
185+
```csharp
186+
// Using certificate and key data directly
187+
var clientCertData = File.ReadAllText("/path/to/client.pem");
188+
var clientKeyData = File.ReadAllText("/path/to/client.key");
189+
190+
var tlsConfig = TlsConfig.WithMutualTlsData(clientCertData, clientKeyData);
191+
192+
var options = new ClientOptions
193+
{
194+
Url = "https://flipt.example.com",
195+
TlsConfig = tlsConfig,
196+
Authentication = new Authentication { ClientToken = "your-token" }
197+
};
198+
199+
using var client = new FliptClient(options);
200+
```
201+
202+
#### Advanced TLS Configuration
203+
204+
```csharp
205+
// Full TLS configuration with all options
206+
var tlsConfig = new TlsConfig
207+
{
208+
CaCertFile = "/path/to/ca.pem",
209+
ClientCertFile = "/path/to/client.pem",
210+
ClientKeyFile = "/path/to/client.key",
211+
InsecureSkipVerify = false
212+
};
213+
214+
var options = new ClientOptions
215+
{
216+
Url = "https://flipt.example.com",
217+
TlsConfig = tlsConfig,
218+
Authentication = new Authentication { ClientToken = "your-token" }
219+
};
220+
221+
using var client = new FliptClient(options);
222+
```
223+
224+
#### Development Mode (Insecure)
225+
226+
**⚠️ WARNING: Only use this in development environments!**
227+
228+
```csharp
229+
// Skip certificate verification (NOT for production)
230+
var tlsConfig = TlsConfig.Insecure();
231+
232+
var options = new ClientOptions
233+
{
234+
Url = "https://localhost:8443",
235+
TlsConfig = tlsConfig,
236+
Authentication = new Authentication { ClientToken = "your-token" }
237+
};
238+
239+
using var client = new FliptClient(options);
240+
```
241+
242+
#### TLS Configuration Options
243+
244+
The `TlsConfig` class supports the following properties:
245+
246+
- `CaCertFile`: Path to custom CA certificate file (PEM format)
247+
- `CaCertData`: Raw CA certificate content (PEM format) - takes precedence over `CaCertFile`
248+
- `InsecureSkipVerify`: Skip certificate verification (development only)
249+
- `ClientCertFile`: Client certificate file for mutual TLS (PEM format)
250+
- `ClientKeyFile`: Client private key file for mutual TLS (PEM format)
251+
- `ClientCertData`: Raw client certificate content (PEM format) - takes precedence over `ClientCertFile`
252+
- `ClientKeyData`: Raw client private key content (PEM format) - takes precedence over `ClientKeyFile`
253+
254+
> **Note**: When both file paths and data are provided, the data properties take precedence. For example, if both `CaCertFile` and `CaCertData` are set, `CaCertData` will be used.
255+
126256
### Error Strategies
127257

128258
The `FliptClient` supports the following error strategies:

flipt-client-csharp/src/FliptClient/Models/ClientOptions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ public class ClientOptions
7373
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
7474
[JsonPropertyName("snapshot")]
7575
public string? Snapshot { get; set; }
76+
77+
/// <summary>
78+
/// Gets or sets TLS configuration for connecting to servers with custom certificates.
79+
/// </summary>
80+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
81+
[JsonPropertyName("tls_config")]
82+
public TlsConfig? TlsConfig { get; set; }
7683
}
7784

7885
[JsonConverter(typeof(LowercaseEnumConverter<ErrorStrategy>))]
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace FliptClient.Models
4+
{
5+
/// <summary>
6+
/// Configuration for TLS connections to Flipt servers.
7+
/// Provides options for custom CA certificates, client certificates for mutual TLS,
8+
/// and insecure mode for development.
9+
/// </summary>
10+
public class TlsConfig
11+
{
12+
/// <summary>
13+
/// Gets or sets the path to the CA certificate file (PEM format).
14+
/// </summary>
15+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
16+
[JsonPropertyName("ca_cert_file")]
17+
public string? CaCertFile { get; set; }
18+
19+
/// <summary>
20+
/// Gets or sets the CA certificate content (PEM format).
21+
/// Takes precedence over CaCertFile if both are provided.
22+
/// </summary>
23+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
24+
[JsonPropertyName("ca_cert_data")]
25+
public string? CaCertData { get; set; }
26+
27+
/// <summary>
28+
/// Gets or sets whether to skip certificate verification.
29+
/// WARNING: Only use this in development environments!
30+
/// </summary>
31+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
32+
[JsonPropertyName("insecure_skip_verify")]
33+
public bool? InsecureSkipVerify { get; set; }
34+
35+
/// <summary>
36+
/// Gets or sets the path to the client certificate file for mutual TLS (PEM format).
37+
/// </summary>
38+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
39+
[JsonPropertyName("client_cert_file")]
40+
public string? ClientCertFile { get; set; }
41+
42+
/// <summary>
43+
/// Gets or sets the path to the client private key file for mutual TLS (PEM format).
44+
/// </summary>
45+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
46+
[JsonPropertyName("client_key_file")]
47+
public string? ClientKeyFile { get; set; }
48+
49+
/// <summary>
50+
/// Gets or sets the client certificate content for mutual TLS (PEM format).
51+
/// Takes precedence over ClientCertFile if both are provided.
52+
/// </summary>
53+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
54+
[JsonPropertyName("client_cert_data")]
55+
public string? ClientCertData { get; set; }
56+
57+
/// <summary>
58+
/// Gets or sets the client private key content for mutual TLS (PEM format).
59+
/// Takes precedence over ClientKeyFile if both are provided.
60+
/// </summary>
61+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
62+
[JsonPropertyName("client_key_data")]
63+
public string? ClientKeyData { get; set; }
64+
65+
/// <summary>
66+
/// Initializes a new instance of the <see cref="TlsConfig"/> class.
67+
/// </summary>
68+
public TlsConfig()
69+
{
70+
}
71+
72+
/// <summary>
73+
/// Creates a TLS configuration for insecure connections (development only).
74+
/// WARNING: Only use this in development environments!
75+
/// </summary>
76+
/// <returns>A TLS configuration with certificate verification disabled.</returns>
77+
public static TlsConfig Insecure()
78+
{
79+
return new TlsConfig
80+
{
81+
InsecureSkipVerify = true
82+
};
83+
}
84+
85+
/// <summary>
86+
/// Creates a TLS configuration with a custom CA certificate file.
87+
/// </summary>
88+
/// <param name="caCertFile">Path to the CA certificate file (PEM format).</param>
89+
/// <returns>A TLS configuration with the specified CA certificate.</returns>
90+
/// <exception cref="ValidationException">Thrown when the CA certificate file does not exist.</exception>
91+
public static TlsConfig WithCaCertFile(string caCertFile)
92+
{
93+
if (string.IsNullOrWhiteSpace(caCertFile))
94+
{
95+
throw new ValidationException("CA certificate file path cannot be null or empty");
96+
}
97+
98+
if (!File.Exists(caCertFile))
99+
{
100+
throw new ValidationException($"CA certificate file does not exist: {caCertFile}");
101+
}
102+
103+
return new TlsConfig
104+
{
105+
CaCertFile = caCertFile
106+
};
107+
}
108+
109+
/// <summary>
110+
/// Creates a TLS configuration with CA certificate data.
111+
/// </summary>
112+
/// <param name="caCertData">CA certificate content in PEM format.</param>
113+
/// <returns>A TLS configuration with the specified CA certificate data.</returns>
114+
/// <exception cref="ValidationException">Thrown when the CA certificate data is null or empty.</exception>
115+
public static TlsConfig WithCaCertData(string caCertData)
116+
{
117+
if (string.IsNullOrWhiteSpace(caCertData))
118+
{
119+
throw new ValidationException("CA certificate data cannot be null or empty");
120+
}
121+
122+
return new TlsConfig
123+
{
124+
CaCertData = caCertData
125+
};
126+
}
127+
128+
/// <summary>
129+
/// Creates a TLS configuration for mutual TLS using certificate files.
130+
/// </summary>
131+
/// <param name="clientCertFile">Path to the client certificate file (PEM format).</param>
132+
/// <param name="clientKeyFile">Path to the client private key file (PEM format).</param>
133+
/// <returns>A TLS configuration for mutual TLS.</returns>
134+
/// <exception cref="ValidationException">Thrown when certificate files are invalid or do not exist.</exception>
135+
public static TlsConfig WithMutualTls(string clientCertFile, string clientKeyFile)
136+
{
137+
if (string.IsNullOrWhiteSpace(clientCertFile))
138+
{
139+
throw new ValidationException("Client certificate file path cannot be null or empty");
140+
}
141+
142+
if (string.IsNullOrWhiteSpace(clientKeyFile))
143+
{
144+
throw new ValidationException("Client key file path cannot be null or empty");
145+
}
146+
147+
if (!File.Exists(clientCertFile))
148+
{
149+
throw new ValidationException($"Client certificate file does not exist: {clientCertFile}");
150+
}
151+
152+
if (!File.Exists(clientKeyFile))
153+
{
154+
throw new ValidationException($"Client key file does not exist: {clientKeyFile}");
155+
}
156+
157+
return new TlsConfig
158+
{
159+
ClientCertFile = clientCertFile,
160+
ClientKeyFile = clientKeyFile
161+
};
162+
}
163+
164+
/// <summary>
165+
/// Creates a TLS configuration for mutual TLS using certificate data.
166+
/// </summary>
167+
/// <param name="clientCertData">Client certificate content in PEM format.</param>
168+
/// <param name="clientKeyData">Client private key content in PEM format.</param>
169+
/// <returns>A TLS configuration for mutual TLS.</returns>
170+
/// <exception cref="ValidationException">Thrown when certificate data is null or empty.</exception>
171+
public static TlsConfig WithMutualTlsData(string clientCertData, string clientKeyData)
172+
{
173+
if (string.IsNullOrWhiteSpace(clientCertData))
174+
{
175+
throw new ValidationException("Client certificate data cannot be null or empty");
176+
}
177+
178+
if (string.IsNullOrWhiteSpace(clientKeyData))
179+
{
180+
throw new ValidationException("Client key data cannot be null or empty");
181+
}
182+
183+
return new TlsConfig
184+
{
185+
ClientCertData = clientCertData,
186+
ClientKeyData = clientKeyData
187+
};
188+
}
189+
}
190+
}

flipt-client-csharp/tests/FliptClient.Tests/FliptClientTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ public FliptClientTests(ITestOutputHelper output)
3535
Authentication = new Authentication { ClientToken = authToken }
3636
};
3737

38+
// Configure TLS if HTTPS URL is provided
39+
if (fliptUrl.StartsWith("https://"))
40+
{
41+
string? caCertPath = Environment.GetEnvironmentVariable("FLIPT_CA_CERT_PATH");
42+
if (!string.IsNullOrEmpty(caCertPath))
43+
{
44+
options.TlsConfig = TlsConfig.WithCaCertFile(caCertPath);
45+
}
46+
else
47+
{
48+
// Fallback to insecure for local testing
49+
options.TlsConfig = TlsConfig.Insecure();
50+
}
51+
}
52+
3853
_client = new FliptClient(options);
3954
}
4055

0 commit comments

Comments
 (0)