Skip to content

Commit d9e3b9c

Browse files
authored
[FFM-7006] - Add support for custom TLS CA certs (#89)
* [FFM-7006] - Add support for custom TLS CA certs What This change allows caller to programmatically configure a self-signed or custom bundle certs without the need to pre-install them on the system Why On-prem deployments may not have a hostname signed with a public CA Testing Manual testing with TLS proxy
1 parent 84a79a8 commit d9e3b9c

File tree

13 files changed

+55
-17
lines changed

13 files changed

+55
-17
lines changed

examples/getting_started/getting_started.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def main():
1919
log.info("Harness SDK Getting Started")
2020
# Create a Feature Flag Client
2121
client = CfClient(api_key)
22+
client.wait_for_initialization()
2223

2324

2425
# Create a target (different targets can get different results based on rules)

featureflags/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
__author__ = """Enver Bisevac"""
44
__email__ = "[email protected]"
5-
__version__ = '1.5.0'
5+
__version__ = '1.6.0'

featureflags/analytics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
VARIATION_VALUE_ATTRIBUTE = 'variationValue'
3535
TARGET_ATTRIBUTE = 'target'
3636
SDK_VERSION_ATTRIBUTE = 'SDK_VERSION'
37-
SDK_VERSION = '1.5.0'
37+
SDK_VERSION = '1.6.0'
3838
SDK_TYPE_ATTRIBUTE = 'SDK_TYPE'
3939
SDK_TYPE = 'server'
4040
SDK_LANGUAGE_ATTRIBUTE = 'SDK_LANGUAGE'

featureflags/api/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Client:
1414
headers: Dict[str, str] = attr.ib(factory=dict, kw_only=True)
1515
timeout: float = attr.ib(30.0, kw_only=True)
1616
max_auth_retries: int
17+
tls_trusted_cas_file: str
1718

1819
def get_headers(self) -> Dict[str, str]:
1920
"""Get headers to be used in all endpoints"""

featureflags/api/default/authenticate.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,19 @@ def _get_kwargs(
3434

3535
json_json_body = json_body.to_dict()
3636

37-
return {
37+
args = {
3838
"url": url,
3939
"headers": headers,
4040
"cookies": cookies,
4141
"timeout": client.get_timeout(),
42-
"json": json_json_body,
42+
"json": json_json_body
4343
}
4444

45+
if client.tls_trusted_cas_file is not None:
46+
args["verify"] = client.tls_trusted_cas_file
47+
48+
return args
49+
4550

4651
def _parse_response(
4752
*, response: httpx.Response

featureflags/api/default/get_all_segments.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,19 @@ def _get_kwargs(
3333
headers: Dict[str, Any] = client.get_headers()
3434
cookies: Dict[str, Any] = client.get_cookies()
3535

36-
return {
36+
args = {
3737
"url": url,
3838
"params": query_params,
3939
"headers": headers,
4040
"cookies": cookies,
4141
"timeout": client.get_timeout(),
4242
}
4343

44+
if client.tls_trusted_cas_file is not None:
45+
args["verify"] = client.tls_trusted_cas_file
46+
47+
return args
48+
4449

4550
def _parse_response(*, response: httpx.Response) -> Optional[List[Segment]]:
4651
if response.status_code == 200:

featureflags/api/default/get_feature_config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,19 @@ def _get_kwargs(
3434
headers: Dict[str, Any] = client.get_headers()
3535
cookies: Dict[str, Any] = client.get_cookies()
3636

37-
return {
37+
args = {
3838
"url": url,
3939
"params": query_params,
4040
"headers": headers,
4141
"cookies": cookies,
4242
"timeout": client.get_timeout(),
4343
}
4444

45+
if client.tls_trusted_cas_file is not None:
46+
args["verify"] = client.tls_trusted_cas_file
47+
48+
return args
49+
4550

4651
def _parse_response(
4752
*,

featureflags/api/default/post_metrics.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def _get_kwargs(
2929

3030
json_json_body = json_body.to_dict()
3131

32-
return {
32+
args = {
3333
"url": url,
3434
"params": query_params,
3535
"headers": headers,
@@ -38,6 +38,11 @@ def _get_kwargs(
3838
"json": json_json_body,
3939
}
4040

41+
if client.tls_trusted_cas_file is not None:
42+
args["verify"] = client.tls_trusted_cas_file
43+
44+
return args
45+
4146

4247
def _build_response(*, response: httpx.Response) -> Response[None]:
4348
return Response(

featureflags/client.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import featureflags.sdk_logging_codes as sdk_codes
2424
from .util import log
2525

26-
VERSION: str = "1.0"
26+
VERSION: str = "1.6.0"
2727

2828

2929
class MissingOrEmptyAPIKeyException(Exception):
@@ -175,7 +175,8 @@ def authenticate(self):
175175
client = Client(
176176
base_url=self._config.base_url,
177177
events_url=self._config.events_url,
178-
max_auth_retries=self._config.max_auth_retries
178+
max_auth_retries=self._config.max_auth_retries,
179+
tls_trusted_cas_file=self._config.tls_trusted_cas_file
179180
)
180181
body = AuthenticationRequest(api_key=self._sdk_key)
181182
response = authenticate(client=client, json_body=body)
@@ -194,7 +195,8 @@ def authenticate(self):
194195
params={
195196
'cluster': self._cluster
196197
},
197-
max_auth_retries=self._config.max_auth_retries
198+
max_auth_retries=self._config.max_auth_retries,
199+
tls_trusted_cas_file=self._config.tls_trusted_cas_file
198200
)
199201
# Additional headers used to track usage
200202
additional_headers = {

featureflags/config.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ def __init__(
2828
store: object = None,
2929
enable_stream: bool = True,
3030
enable_analytics: bool = True,
31-
max_auth_retries: int = 10
31+
max_auth_retries: int = 10,
32+
tls_trusted_cas_file: str = None
3233
):
3334
self.base_url = base_url
3435
self.events_url = events_url
@@ -48,6 +49,7 @@ def __init__(
4849
self.enable_stream = enable_stream
4950
self.enable_analytics = enable_analytics
5051
self.max_auth_retries = max_auth_retries
52+
self.tls_trusted_cas_file = tls_trusted_cas_file
5153

5254

5355
default_config = Config()
@@ -93,3 +95,15 @@ def func(config: Config) -> None:
9395
config.max_auth_retries = value
9496

9597
return func
98+
99+
100+
def with_tls_trusted_cas_file(value: str) -> Callable:
101+
"""
102+
This config item is for on-prem or proxy customers using custom TLS certs.
103+
It takes a filename of a CA bundle. It should include all intermediate CAs
104+
and the root CA (concatenated in PEM format).
105+
"""
106+
def func(config: Config) -> None:
107+
config.tls_trusted_cas_file = value
108+
109+
return func

0 commit comments

Comments
 (0)