Skip to content

Commit 5d8d03f

Browse files
feat: Adding TLS support for offline server. (#4744)
* * Adding TLS support for offline server. * Added test cases for the TLS offline server by creating RemoteOfflineTlsStoreDataSourceCreator Signed-off-by: lrangine <19699092+lokeshrangineni@users.noreply.github.com> * * Fixing the lint error and also integration tests. Signed-off-by: lrangine <19699092+lokeshrangineni@users.noreply.github.com> * * Added documentation for the offline server and moved to how to guide. * Fixing the issue with integration test. Signed-off-by: lrangine <19699092+lokeshrangineni@users.noreply.github.com> * * Added documentation for the offline server and moved to how to guide. * Fixing the issue with integration test. Signed-off-by: lrangine <19699092+lokeshrangineni@users.noreply.github.com> * * fixing the integration test by adding extra flag verify_client Signed-off-by: lrangine <19699092+lokeshrangineni@users.noreply.github.com> * * Adding alias names for the host in self-signed certificate. Signed-off-by: lrangine <19699092+lokeshrangineni@users.noreply.github.com> --------- Signed-off-by: lrangine <19699092+lokeshrangineni@users.noreply.github.com>
1 parent 3bad4a1 commit 5d8d03f

File tree

10 files changed

+303
-36
lines changed

10 files changed

+303
-36
lines changed

docs/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
* [Adding a new online store](how-to-guides/customizing-feast/adding-support-for-a-new-online-store.md)
6565
* [Adding a custom provider](how-to-guides/customizing-feast/creating-a-custom-provider.md)
6666
* [Adding or reusing tests](how-to-guides/adding-or-reusing-tests.md)
67+
* [Starting Feast servers in TLS(SSL) Mode](how-to-guides/starting-feast-servers-tls-mode.md)
6768

6869
## Reference
6970

docs/reference/starting-feast-servers-tls-mode.md renamed to docs/how-to-guides/starting-feast-servers-tls-mode.md

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Starting feast servers in TLS (SSL) mode.
22
TLS (Transport Layer Security) and SSL (Secure Sockets Layer) are both protocols encrypts communications between a client and server to provide enhanced security.TLS or SSL words used interchangeably.
33
This article is going to show the sample code to start all the feast servers such as online server, offline server, registry server and UI server in TLS mode.
4-
Also show examples related to feast clients to communicate with the feast servers started in TLS mode.
4+
Also show examples related to feast clients to communicate with the feast servers started in TLS mode.
5+
6+
We assume you have basic understanding of feast terminology before going through this tutorial, if you are new to feast then we would recommend to go through existing [starter tutorials](./../../examples) of feast.
57

68
## Obtaining a self-signed TLS certificate and key
79
In development mode we can generate a self-signed certificate for testing. In an actual production environment it is always recommended to get it from a trusted TLS certificate provider.
@@ -17,15 +19,32 @@ The above command will generate two files
1719
You can use the public or private keys generated from above command in the rest of the sections in this tutorial.
1820

1921
## Create the feast demo repo for the rest of the sections.
20-
create a feast repo using `feast init` command and use this repo as a demo for subsequent sections.
22+
Create a feast repo and initialize using `feast init` and `feast apply` command and use this repo as a demo for subsequent sections.
2123

2224
```shell
2325
feast init feast_repo_ssl_demo
24-
```
2526

26-
Output is
27-
```
27+
#output will be something similar as below
2828
Creating a new Feast repository in /Documents/Src/feast/feast_repo_ssl_demo.
29+
30+
cd feast_repo_ssl_demo/feature_repo
31+
feast apply
32+
33+
#output will be something similar as below
34+
Applying changes for project feast_repo_ssl_demo
35+
36+
Created project feast_repo_ssl_demo
37+
Created entity driver
38+
Created feature view driver_hourly_stats
39+
Created feature view driver_hourly_stats_fresh
40+
Created on demand feature view transformed_conv_rate
41+
Created on demand feature view transformed_conv_rate_fresh
42+
Created feature service driver_activity_v1
43+
Created feature service driver_activity_v3
44+
Created feature service driver_activity_v2
45+
46+
Created sqlite table feast_repo_ssl_demo_driver_hourly_stats_fresh
47+
Created sqlite table feast_repo_ssl_demo_driver_hourly_stats
2948
```
3049

3150
You need to execute the feast cli commands from `feast_repo_ssl_demo/feature_repo` directory created from the above `feast init` command.
@@ -68,7 +87,7 @@ entity_key_serialization_version: 2
6887
auth:
6988
type: no_auth
7089
```
71-
{% endcode %}
90+
7291
7392
`cert` is an optional configuration to the public certificate path when the online server starts in TLS(SSL) mode. Typically, this file ends with `*.crt`, `*.cer`, or `*.pem`.
7493

@@ -106,14 +125,55 @@ entity_key_serialization_version: 2
106125
auth:
107126
type: no_auth
108127
```
109-
{% endcode %}
110128

111129
`cert` is an optional configuration to the public certificate path when the registry server starts in TLS(SSL) mode. Typically, this file ends with `*.crt`, `*.cer`, or `*.pem`.
112130

113131
## Starting feast offline server in TLS mode
114132

115-
TBD
133+
To start the offline server in TLS mode, you need to provide the private and public keys using the `--key` and `--cert` arguments with the `feast serve_offline` command.
134+
135+
```shell
136+
feast serve_offline --key /path/to/key.pem --cert /path/to/cert.pem
137+
```
138+
You will see the output something similar to as below. Note the server url starts in the `https` mode.
139+
140+
```shell
141+
11/07/2024 11:10:01 AM feast.offline_server INFO: Found SSL certificates in the args so going to start offline server in TLS(SSL) mode.
142+
11/07/2024 11:10:01 AM feast.offline_server INFO: Offline store server serving at: grpc+tls://127.0.0.1:8815
143+
11/07/2024 11:10:01 AM feast.offline_server INFO: offline server starting with pid: [11606]
144+
```
145+
146+
### Feast client connecting to remote offline sever started in TLS mode.
147+
148+
Sometimes you may need to pass the self-signed public key to connect to the remote registry server started in SSL mode if you have not added the public key to the certificate store.
149+
You have to add `scheme` to `https`.
150+
151+
feast client example:
116152

153+
```yaml
154+
project: feast-project
155+
registry:
156+
registry_type: remote
157+
path: https://localhost:6570
158+
cert: /path/to/cert.pem
159+
provider: local
160+
online_store:
161+
path: http://localhost:6566
162+
type: remote
163+
cert: /path/to/cert.pem
164+
entity_key_serialization_version: 2
165+
offline_store:
166+
type: remote
167+
host: localhost
168+
port: 8815
169+
scheme: https
170+
cert: /path/to/cert.pem
171+
auth:
172+
type: no_auth
173+
```
174+
175+
`cert` is an optional configuration to the public certificate path when the registry server starts in TLS(SSL) mode. Typically, this file ends with `*.crt`, `*.cer`, or `*.pem`.
176+
`scheme` should be `https`. By default, it will be `http` so you have to explicitly configure to `https` if you are planning to connect to remote offline server which is started in TLS mode.
117177

118178
## Starting feast UI server (react app) in TLS mode
119179
To start the feast UI server in TLS mode, you need to provide the private and public keys using the `--key` and `--cert` arguments with the `feast ui` command.

sdk/python/feast/cli.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1114,16 +1114,50 @@ def serve_registry_command(
11141114
default=DEFAULT_OFFLINE_SERVER_PORT,
11151115
help="Specify a port for the server",
11161116
)
1117+
@click.option(
1118+
"--key",
1119+
"-k",
1120+
"tls_key_path",
1121+
type=click.STRING,
1122+
default="",
1123+
show_default=False,
1124+
help="path to TLS certificate private key. You need to pass --cert as well to start server in TLS mode",
1125+
)
1126+
@click.option(
1127+
"--cert",
1128+
"-c",
1129+
"tls_cert_path",
1130+
type=click.STRING,
1131+
default="",
1132+
show_default=False,
1133+
help="path to TLS certificate public key. You need to pass --key as well to start server in TLS mode",
1134+
)
1135+
@click.option(
1136+
"--verify_client",
1137+
"-v",
1138+
"tls_verify_client",
1139+
type=click.BOOL,
1140+
default="True",
1141+
show_default=True,
1142+
help="Verify the client or not for the TLS client certificate.",
1143+
)
11171144
@click.pass_context
11181145
def serve_offline_command(
11191146
ctx: click.Context,
11201147
host: str,
11211148
port: int,
1149+
tls_key_path: str,
1150+
tls_cert_path: str,
1151+
tls_verify_client: bool,
11221152
):
11231153
"""Start a remote server locally on a given host, port."""
1154+
if (tls_key_path and not tls_cert_path) or (not tls_key_path and tls_cert_path):
1155+
raise click.BadParameter(
1156+
"Please pass --cert and --key args to start the offline server in TLS mode."
1157+
)
11241158
store = create_feature_store(ctx)
11251159

1126-
store.serve_offline(host, port)
1160+
store.serve_offline(host, port, tls_key_path, tls_cert_path, tls_verify_client)
11271161

11281162

11291163
@cli.command("validate")

sdk/python/feast/feature_store.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1958,11 +1958,20 @@ def serve_registry(
19581958
self, port=port, tls_key_path=tls_key_path, tls_cert_path=tls_cert_path
19591959
)
19601960

1961-
def serve_offline(self, host: str, port: int) -> None:
1961+
def serve_offline(
1962+
self,
1963+
host: str,
1964+
port: int,
1965+
tls_key_path: str = "",
1966+
tls_cert_path: str = "",
1967+
tls_verify_client: bool = True,
1968+
) -> None:
19621969
"""Start offline server locally on a given port."""
19631970
from feast import offline_server
19641971

1965-
offline_server.start_server(self, host, port)
1972+
offline_server.start_server(
1973+
self, host, port, tls_key_path, tls_cert_path, tls_verify_client
1974+
)
19661975

19671976
def serve_transformations(self, port: int) -> None:
19681977
"""Start the feature transformation server locally on a given port."""

sdk/python/feast/infra/offline_stores/remote.py

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,45 @@ def list_actions(self, options: FlightCallOptions = None):
7070
return super().list_actions(options)
7171

7272

73-
def build_arrow_flight_client(host: str, port, auth_config: AuthConfig):
73+
def build_arrow_flight_client(
74+
scheme: str, host: str, port, auth_config: AuthConfig, cert: str = ""
75+
):
76+
arrow_scheme = "grpc+tcp"
77+
if cert:
78+
logger.info(
79+
"Scheme is https so going to connect offline server in SSL(TLS) mode."
80+
)
81+
arrow_scheme = "grpc+tls"
82+
83+
kwargs = {}
84+
if cert:
85+
with open(cert, "rb") as root_certs:
86+
kwargs["tls_root_certs"] = root_certs.read()
87+
7488
if auth_config.type != AuthType.NONE.value:
7589
middlewares = [FlightAuthInterceptorFactory(auth_config)]
76-
return FeastFlightClient(f"grpc://{host}:{port}", middleware=middlewares)
90+
return FeastFlightClient(
91+
f"{arrow_scheme}://{host}:{port}", middleware=middlewares, **kwargs
92+
)
7793

78-
return FeastFlightClient(f"grpc://{host}:{port}")
94+
return FeastFlightClient(f"{arrow_scheme}://{host}:{port}", **kwargs)
7995

8096

8197
class RemoteOfflineStoreConfig(FeastConfigBaseModel):
8298
type: Literal["remote"] = "remote"
99+
100+
scheme: Literal["http", "https"] = "http"
101+
83102
host: StrictStr
84103
""" str: remote offline store server port, e.g. the host URL for offline store of arrow flight server. """
85104

86105
port: Optional[StrictInt] = None
87106
""" str: remote offline store server port."""
88107

108+
cert: StrictStr = ""
109+
""" str: Path to the public certificate when the offline server starts in TLS(SSL) mode. This may be needed if the offline server started with a self-signed certificate, typically this file ends with `*.crt`, `*.cer`, or `*.pem`.
110+
If type is 'remote', then this configuration is needed to connect to remote offline server in TLS mode. """
111+
89112

90113
class RemoteRetrievalJob(RetrievalJob):
91114
def __init__(
@@ -178,7 +201,11 @@ def get_historical_features(
178201
assert isinstance(config.offline_store, RemoteOfflineStoreConfig)
179202

180203
client = build_arrow_flight_client(
181-
config.offline_store.host, config.offline_store.port, config.auth_config
204+
scheme=config.offline_store.scheme,
205+
host=config.offline_store.host,
206+
port=config.offline_store.port,
207+
auth_config=config.auth_config,
208+
cert=config.offline_store.cert,
182209
)
183210

184211
feature_view_names = [fv.name for fv in feature_views]
@@ -214,7 +241,11 @@ def pull_all_from_table_or_query(
214241

215242
# Initialize the client connection
216243
client = build_arrow_flight_client(
217-
config.offline_store.host, config.offline_store.port, config.auth_config
244+
scheme=config.offline_store.scheme,
245+
host=config.offline_store.host,
246+
port=config.offline_store.port,
247+
auth_config=config.auth_config,
248+
cert=config.offline_store.cert,
218249
)
219250

220251
api_parameters = {
@@ -247,7 +278,11 @@ def pull_latest_from_table_or_query(
247278

248279
# Initialize the client connection
249280
client = build_arrow_flight_client(
250-
config.offline_store.host, config.offline_store.port, config.auth_config
281+
config.offline_store.scheme,
282+
config.offline_store.host,
283+
config.offline_store.port,
284+
config.auth_config,
285+
cert=config.offline_store.cert,
251286
)
252287

253288
api_parameters = {
@@ -282,7 +317,11 @@ def write_logged_features(
282317

283318
# Initialize the client connection
284319
client = build_arrow_flight_client(
285-
config.offline_store.host, config.offline_store.port, config.auth_config
320+
config.offline_store.scheme,
321+
config.offline_store.host,
322+
config.offline_store.port,
323+
config.auth_config,
324+
config.offline_store.cert,
286325
)
287326

288327
api_parameters = {
@@ -308,7 +347,11 @@ def offline_write_batch(
308347

309348
# Initialize the client connection
310349
client = build_arrow_flight_client(
311-
config.offline_store.host, config.offline_store.port, config.auth_config
350+
config.offline_store.scheme,
351+
config.offline_store.host,
352+
config.offline_store.port,
353+
config.auth_config,
354+
config.offline_store.cert,
312355
)
313356

314357
feature_view_names = [feature_view.name]
@@ -336,7 +379,11 @@ def validate_data_source(
336379
assert isinstance(config.offline_store, RemoteOfflineStoreConfig)
337380

338381
client = build_arrow_flight_client(
339-
config.offline_store.host, config.offline_store.port, config.auth_config
382+
config.offline_store.scheme,
383+
config.offline_store.host,
384+
config.offline_store.port,
385+
config.auth_config,
386+
config.offline_store.cert,
340387
)
341388

342389
api_parameters = {
@@ -357,7 +404,11 @@ def get_table_column_names_and_types_from_data_source(
357404
assert isinstance(config.offline_store, RemoteOfflineStoreConfig)
358405

359406
client = build_arrow_flight_client(
360-
config.offline_store.host, config.offline_store.port, config.auth_config
407+
config.offline_store.scheme,
408+
config.offline_store.host,
409+
config.offline_store.port,
410+
config.auth_config,
411+
config.offline_store.cert,
361412
)
362413

363414
api_parameters = {

0 commit comments

Comments
 (0)