Skip to content

Commit c9602d8

Browse files
jacob-bush-shopifypi-monofranciscojavierarceo
authored
feat: Add mTLS support to remote registry gRPC client (#6474)
Add client certificate authentication (mutual TLS) to RemoteRegistry so it can connect to gRPC servers that require client certificates. New config fields on RemoteRegistryConfig: - client_cert: path to the client certificate (PEM) - client_key: path to the client private key (PEM) - authority: override the gRPC :authority header for TLS hostname verification (required when connecting through a tunnel or proxy) The existing cert field continues to serve as the CA certificate for server verification. Signed-off-by: Jacob Bush <[email protected]> Co-authored-by: AI (Pi/Claude Opus 4.6 [250k]) <[email protected]> Co-authored-by: Francisco Javier Arceo <[email protected]>
1 parent bad2b7d commit c9602d8

5 files changed

Lines changed: 552 additions & 10 deletions

File tree

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,52 @@ auth:
128128

129129
`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`.
130130

131+
### Feast client connecting to remote registry server with mTLS
132+
133+
If the Registry Server requires mutual TLS (mTLS), the client must present a certificate and private key in addition to trusting the server's CA certificate. Add `client_cert` and `client_key` to the registry configuration:
134+
135+
```yaml
136+
project: feast-project
137+
registry:
138+
registry_type: remote
139+
path: feature-registry.example.com:443
140+
cert: /path/to/ca.crt
141+
client_cert: /path/to/tls.crt
142+
client_key: /path/to/tls.key
143+
provider: local
144+
online_store:
145+
path: http://localhost:6566
146+
type: remote
147+
entity_key_serialization_version: 3
148+
auth:
149+
type: no_auth
150+
```
151+
152+
* `cert` — CA certificate used to verify the server (or use the `SSL_CERT_FILE` / `REQUESTS_CA_BUNDLE` environment variable).
153+
* `client_cert` — Client certificate presented to the server. Must be paired with `client_key`.
154+
* `client_key` — Private key for the client certificate.
155+
156+
#### Connecting through a tunnel or proxy
157+
158+
When connecting through a tunnel (e.g. `gcloud compute start-iap-tunnel`) the client connects to `localhost`, but the server certificate is issued for the real service hostname. Set the `authority` field so that gRPC's TLS hostname verification passes:
159+
160+
```shell
161+
# In one terminal — start the tunnel:
162+
gcloud compute start-iap-tunnel feature-registry.example.com 443 --local-host-port=localhost:8443
163+
```
164+
165+
```yaml
166+
registry:
167+
registry_type: remote
168+
path: localhost:8443
169+
cert: /path/to/ca.crt
170+
client_cert: /path/to/tls.crt
171+
client_key: /path/to/tls.key
172+
authority: feature-registry.example.com
173+
```
174+
175+
Without `authority`, the gRPC client would check the server certificate against `localhost`, which would fail because the certificate's Subject Alternative Name (SAN) is `feature-registry.example.com`.
176+
131177
## Starting feast offline server in TLS mode
132178

133179
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.

‎docs/reference/registries/remote.md‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,27 @@ registry:
1818
{% endcode %}
1919
2020
The optional `cert` parameter can be configured as well, it should point to the public certificate path when the Registry Server starts in SSL mode. This may be needed if the Registry Server is started with a self-signed certificate, typically this file ends with *.crt, *.cer, or *.pem.
21-
More info about the `cert` parameter can be found in [feast-client-connecting-to-remote-registry-sever-started-in-tls-mode](../../how-to-guides/starting-feast-servers-tls-mode.md#feast-client-connecting-to-remote-registry-sever-started-in-tls-mode)
21+
22+
For **mutual TLS (mTLS)**, you can also configure:
23+
* `client_cert` — Path to the client certificate presented to the server. Must be paired with `client_key`. Typically ends with `*.crt` or `*.pem`.
24+
* `client_key` — Path to the client private key. Must be paired with `client_cert`. Typically ends with `*.key` or `*.pem`.
25+
26+
When connecting through a tunnel or proxy where the connection address differs from the server hostname, set:
27+
* `authority` — Overrides the gRPC `:authority` header so the server certificate is validated against the correct hostname.
28+
29+
{% code title="feature_store.yaml" %}
30+
```yaml
31+
registry:
32+
registry_type: remote
33+
path: localhost:8443
34+
cert: /path/to/ca.crt
35+
client_cert: /path/to/tls.crt
36+
client_key: /path/to/tls.key
37+
authority: feature-registry.example.com
38+
```
39+
{% endcode %}
40+
41+
More info about TLS configuration can be found in [feast-client-connecting-to-remote-registry-sever-started-in-tls-mode](../../how-to-guides/starting-feast-servers-tls-mode.md#feast-client-connecting-to-remote-registry-sever-started-in-tls-mode)
2242

2343
## How to configure the server
2444

‎sdk/python/feast/infra/registry/remote.py‎

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,25 @@ class RemoteRegistryConfig(RegistryConfig):
7474

7575
is_tls: bool = False
7676
""" bool: Set to `True` if you plan to connect to a registry server running in TLS (SSL) mode.
77-
If you intend to add the public certificate to the trust store instead of passing it via the `cert` parameter, this field must be set to `True`.
78-
If you are planning to add the public certificate as part of the trust store instead of passing it as a `cert` parameters then setting this field to `true` is mandatory.
77+
If you are planning to add the public certificate as part of the trust store instead of passing it as a `cert` parameters then setting this field to `True` is mandatory.
7978
"""
8079

80+
client_cert: StrictStr = ""
81+
""" str: Path to the client certificate for mTLS (mutual TLS) authentication.
82+
Required when connecting to a server that enforces mutual TLS.
83+
Must be provided together with `client_key`.
84+
Typically this file ends with `*.crt` or `*.pem`. """
85+
86+
client_key: StrictStr = ""
87+
""" str: Path to the client private key for mTLS (mutual TLS) authentication.
88+
Must be provided together with `client_cert`.
89+
Typically this file ends with `*.key` or `*.pem`. """
90+
91+
authority: StrictStr = ""
92+
""" str: Override the gRPC :authority header for TLS connections.
93+
Required when the connection address differs from the service hostname,
94+
e.g. when connecting through a tunnel or proxy for local development. """
95+
8196

8297
class RemoteRegistry(BaseRegistry):
8398
def __init__(
@@ -99,19 +114,45 @@ def __init__(
99114
def _create_grpc_channel(self, registry_config):
100115
assert isinstance(registry_config, RemoteRegistryConfig)
101116
if registry_config.cert or registry_config.is_tls:
102-
cafile = os.getenv("SSL_CERT_FILE") or os.getenv("REQUESTS_CA_BUNDLE")
103-
if not cafile and not registry_config.cert:
117+
cafile = (
118+
registry_config.cert
119+
or os.getenv("SSL_CERT_FILE")
120+
or os.getenv("REQUESTS_CA_BUNDLE")
121+
)
122+
if not cafile:
104123
raise EnvironmentError(
105124
"SSL_CERT_FILE or REQUESTS_CA_BUNDLE environment variable must be set to use secure TLS or set the cert parameter in feature_Store.yaml file under remote registry configuration."
106125
)
107-
with open(
108-
registry_config.cert if registry_config.cert else cafile, "rb"
109-
) as cert_file:
126+
if (registry_config.client_cert and not registry_config.client_key) or (
127+
not registry_config.client_cert and registry_config.client_key
128+
):
129+
raise ValueError(
130+
"Both client_cert and client_key must be provided for mTLS. "
131+
"Only one was set in the remote registry configuration."
132+
)
133+
134+
with open(cafile, "rb") as cert_file:
110135
trusted_certs = cert_file.read()
136+
private_key: Optional[bytes] = None
137+
certificate_chain: Optional[bytes] = None
138+
if registry_config.client_cert and registry_config.client_key:
139+
with open(registry_config.client_key, "rb") as key_file:
140+
private_key = key_file.read()
141+
with open(registry_config.client_cert, "rb") as cert_file:
142+
certificate_chain = cert_file.read()
111143
tls_credentials = grpc.ssl_channel_credentials(
112-
root_certificates=trusted_certs
144+
root_certificates=trusted_certs,
145+
private_key=private_key,
146+
certificate_chain=certificate_chain,
147+
)
148+
149+
options = []
150+
if registry_config.authority:
151+
options.append(("grpc.default_authority", registry_config.authority))
152+
153+
return grpc.secure_channel(
154+
registry_config.path, tls_credentials, options=options
113155
)
114-
return grpc.secure_channel(registry_config.path, tls_credentials)
115156
else:
116157
# Create an insecure gRPC channel
117158
return grpc.insecure_channel(registry_config.path)

0 commit comments

Comments
 (0)