Skip to content

Commit 28bde01

Browse files
committed
feat: Add ConnectionRef to DataSource for pluggable external credential resolution
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
1 parent 52999f1 commit 28bde01

27 files changed

Lines changed: 1235 additions & 28 deletions

File tree

docs/reference/data-sources/overview.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,79 @@ However, not every batch data source supports all of these types.
1010

1111
For more details on the Feast type system, see [here](../type-system.md).
1212

13+
## Per-DataSource Credentials (ConnectionRef)
14+
15+
By default, every data source inherits connection credentials from the global `feature_store.yaml` offline store configuration. The `ConnectionRef` feature allows each data source to declare its own external credential reference, enabling:
16+
17+
- **Multi-tenant deployments** where different feature views access different accounts or databases.
18+
- **Credential isolation** where secrets are resolved at runtime from external providers (Kubernetes Secrets, HashiCorp Vault, environment variables) rather than stored in configuration files.
19+
- **Hybrid offline store** routing where a single Feast deployment connects to multiple backends, each with independent credentials.
20+
21+
### ConnectionRef structure
22+
23+
A `ConnectionRef` is attached to any data source via the `connection_ref` parameter:
24+
25+
```python
26+
from feast.credentials import ConnectionRef
27+
from feast.infra.offline_stores.snowflake_source import SnowflakeSource
28+
29+
source = SnowflakeSource(
30+
table="USER_FEATURES",
31+
connection_ref=ConnectionRef(
32+
provider="kubernetes",
33+
name="snowflake-creds",
34+
namespace="ml-team",
35+
connection_type="snowflake.offline",
36+
auth_type="secret",
37+
params={"account": "xy12345", "warehouse": "COMPUTE_WH"},
38+
),
39+
)
40+
```
41+
42+
| Field | Description | Required |
43+
|-------|-------------|----------|
44+
| `provider` | Credential backend — `"kubernetes"`, `"vault"`, `"env"`, `"aws-secrets-manager"`, `"gcp-secret-manager"`, `"azure-key-vault"` | Yes |
45+
| `name` | Provider-specific identifier — K8s Secret name, Vault path, env-var prefix, etc. | Yes |
46+
| `namespace` | Scope qualifier — K8s namespace, Vault mount, AWS region, etc. | No |
47+
| `connection_type` | Offline store type (e.g., `"snowflake.offline"`, `"bigquery"`, `"spark"`) | No |
48+
| `auth_type` | Authentication mechanism — `"secret"` (default), `"oauth2"`, `"basic"`, `"sigv4"` | No |
49+
| `params` | Non-sensitive connection parameters (account, database, warehouse, endpoint) | No |
50+
51+
### Credential providers
52+
53+
Feast ships with built-in providers that can be registered at startup:
54+
55+
| Provider | Resolves from | `name` is | `namespace` is |
56+
|----------|---------------|-----------|----------------|
57+
| `env` | Environment variables | Variable prefix ||
58+
| `kubernetes` | Kubernetes Secrets | Secret name | K8s namespace |
59+
| `vault` | HashiCorp Vault | Secret path | Vault mount |
60+
61+
Custom providers can be registered via:
62+
63+
```python
64+
from feast.credentials import register_credential_provider, CredentialProvider, ConnectionRef
65+
66+
class MyProvider(CredentialProvider):
67+
def provider_type(self) -> str:
68+
return "my-provider"
69+
70+
def resolve(self, ref: ConnectionRef) -> dict:
71+
# Return key-value credential pairs
72+
return {"username": "...", "password": "..."}
73+
74+
register_credential_provider(MyProvider())
75+
```
76+
77+
### How it works
78+
79+
1. When an offline store needs to connect, it checks whether the data source has a `connection_ref`.
80+
2. If present, credentials are resolved from the external provider at runtime.
81+
3. Resolved credentials (and any `params` from the `ConnectionRef`) are merged and used to override the global offline store configuration for that specific operation.
82+
4. If no `connection_ref` is set, the data source uses the global `feature_store.yaml` configuration as before.
83+
84+
For usage with the Hybrid Offline Store, see [Hybrid Offline Store](../offline-stores/hybrid.md).
85+
1386
## Functionality Matrix
1487

1588
There are currently four core batch data source implementations: `FileSource`, `BigQuerySource`, `SnowflakeSource`, and `RedshiftSource`.

docs/reference/offline-stores/hybrid.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,84 @@ store.materialize(
8282
)
8383
```
8484

85+
## Using ConnectionRef with Hybrid Offline Store
86+
87+
When using the HybridOfflineStore, each data source can carry its own credentials via `ConnectionRef`. This is particularly useful when different feature views connect to different accounts or clusters — you no longer need to embed all credentials in `feature_store.yaml`.
88+
89+
### Example: Per-DataSource Credentials
90+
91+
{% code title="feature_store.yaml" %}
92+
```yaml
93+
project: my_feature_repo
94+
registry: data/registry.db
95+
provider: local
96+
offline_store:
97+
type: hybrid_offline_store.HybridOfflineStore
98+
offline_stores:
99+
- type: snowflake.offline
100+
- type: bigquery
101+
```
102+
{% endcode %}
103+
104+
```python
105+
from feast import FeatureView, Entity, ValueType
106+
from feast.credentials import ConnectionRef
107+
from feast.infra.offline_stores.snowflake_source import SnowflakeSource
108+
from feast.infra.offline_stores.bigquery_source import BigQuerySource
109+
110+
entity = Entity(name="user_id", value_type=ValueType.INT64, join_keys=["user_id"])
111+
112+
# Snowflake source with credentials from a Kubernetes Secret
113+
feature_view1 = FeatureView(
114+
name="user_features",
115+
entities=["user_id"],
116+
ttl=None,
117+
source=SnowflakeSource(
118+
table="USER_FEATURES",
119+
connection_ref=ConnectionRef(
120+
provider="kubernetes",
121+
name="snowflake-team-a-creds",
122+
namespace="ml-team",
123+
connection_type="snowflake.offline",
124+
params={"account": "xy12345", "warehouse": "COMPUTE_WH"},
125+
),
126+
),
127+
)
128+
129+
# BigQuery source with credentials from a Kubernetes Secret
130+
feature_view2 = FeatureView(
131+
name="user_activity",
132+
entities=["user_id"],
133+
ttl=None,
134+
source=BigQuerySource(
135+
table="my_project.dataset.user_activity",
136+
connection_ref=ConnectionRef(
137+
provider="kubernetes",
138+
name="bigquery-team-b-creds",
139+
namespace="ml-team",
140+
connection_type="bigquery",
141+
),
142+
),
143+
)
144+
```
145+
146+
In this setup:
147+
- No sensitive credentials are stored in `feature_store.yaml`.
148+
- Each data source resolves its credentials independently at runtime from the referenced Kubernetes Secret.
149+
- The HybridOfflineStore routes operations to the correct backend based on the source type.
150+
151+
### How credential resolution works
152+
153+
1. The HybridOfflineStore determines which backend to use based on the data source class (e.g., `SnowflakeSource` → Snowflake offline store).
154+
2. Before connecting, the offline store checks if the data source has a `connection_ref`.
155+
3. If present, credentials are fetched from the external provider (e.g., reading a Kubernetes Secret).
156+
4. Resolved credentials override the global offline store config for that operation.
157+
5. If no `connection_ref` is set, the global `feature_store.yaml` configuration is used as a fallback.
158+
159+
This pattern is especially valuable in multi-tenant environments where a shared Feast deployment serves multiple teams, each with isolated credentials and backend accounts.
160+
161+
For details on the `ConnectionRef` structure and supported providers, see [Data Sources Overview](../data-sources/overview.md#per-datasource-credentials-connectionref).
162+
85163
## Functionality Matrix
86164
| Feature/Functionality | Supported |
87165
|---------------------------------------------------|----------------------------|

protos/feast/core/DataSource.proto

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,38 @@ import "feast/core/DataFormat.proto";
2828
import "feast/types/Value.proto";
2929
import "feast/core/Feature.proto";
3030

31+
// Connection reference for a DataSource.
32+
// Combines connection type, credential resolution, and non-sensitive
33+
// connection parameters into a single reusable reference.
34+
// Allows DataSources to declare their full connection identity — which
35+
// backend to use, how to authenticate, and where to connect — independent
36+
// of the global offline_store config in feature_store.yaml.
37+
message ConnectionRef {
38+
// Credential provider type: "kubernetes", "vault", "aws-secrets-manager",
39+
// "gcp-secret-manager", "azure-key-vault", "env".
40+
string provider = 1;
41+
42+
// Provider-specific name: K8s Secret name, Vault path, env var prefix, etc.
43+
string name = 2;
44+
45+
// Optional scope qualifier: K8s namespace, Vault mount, AWS region, etc.
46+
string namespace = 3;
47+
48+
// Optional offline store class type (e.g., "snowflake.offline", "bigquery",
49+
// "spark", "iceberg-rest"). When empty, inferred from the DataSource class.
50+
string connection_type = 4;
51+
52+
// Optional authentication mechanism: "secret", "oauth2", "basic", "sigv4".
53+
// Defaults to "secret" (raw key-value credentials from provider).
54+
string auth_type = 5;
55+
56+
// Optional non-sensitive connection parameters (e.g., account, database,
57+
// warehouse, endpoint URI). Keys and values are backend-specific.
58+
map<string, string> params = 6;
59+
}
60+
3161
// Defines a Data Source that can be used source Feature data
32-
// Next available id: 29
62+
// Next available id: 30
3363
message DataSource {
3464
// Field indexes should *not* be reused. Not sure if fields 6-10 were used previously or not,
3565
// but they are going to be reserved for backwards compatibility.
@@ -95,6 +125,13 @@ message DataSource {
95125
// Optional batch source for streaming sources for historical features and materialization.
96126
DataSource batch_source = 26;
97127

128+
// Optional connection reference for this data source.
129+
// When set, OfflineStores resolve connection type and credentials at
130+
// runtime via the registered CredentialProvider instead of using ambient
131+
// env vars or the global offline_store config in feature_store.yaml.
132+
// All fields except provider and name are optional.
133+
ConnectionRef connection_ref = 29;
134+
98135
SourceMeta meta = 50;
99136

100137
message SourceMeta {

0 commit comments

Comments
 (0)