From 84fd67b7f5c2721839f1dd1b66e9fa2ea0878a33 Mon Sep 17 00:00:00 2001 From: amol-patel <121927948+amol-patel@users.noreply.github.com> Date: Mon, 22 Dec 2025 11:19:35 +0530 Subject: [PATCH 1/3] Add role field to snowflake connector - Added 'Role' field to clusterContext struct in snowflake.go. - Updated README to include 'role' configuration example for Snowflake plugin. --- internal/pkg/object/command/snowflake/snowflake.go | 2 ++ plugins/snowflake/README.md | 1 + 2 files changed, 3 insertions(+) diff --git a/internal/pkg/object/command/snowflake/snowflake.go b/internal/pkg/object/command/snowflake/snowflake.go index cf850db..71d5a08 100644 --- a/internal/pkg/object/command/snowflake/snowflake.go +++ b/internal/pkg/object/command/snowflake/snowflake.go @@ -39,6 +39,7 @@ type clusterContext struct { User string `yaml:"user,omitempty" json:"user,omitempty"` Database string `yaml:"database,omitempty" json:"database,omitempty"` Warehouse string `yaml:"warehouse,omitempty" json:"warehouse,omitempty"` + Role string `yaml:"role,omitempty" json:"role,omitempty"` PrivateKey string `yaml:"private_key,omitempty" json:"private_key,omitempty"` } @@ -103,6 +104,7 @@ func (s *commandContext) handler(ctx context.Context, r *plugin.Runtime, j *job. User: clusterContext.User, Database: clusterContext.Database, Warehouse: clusterContext.Warehouse, + Role: clusterContext.Role, Authenticator: sf.AuthTypeJwt, PrivateKey: privateKey, Application: r.UserAgent, diff --git a/plugins/snowflake/README.md b/plugins/snowflake/README.md index 0ccf9c6..97b2c77 100644 --- a/plugins/snowflake/README.md +++ b/plugins/snowflake/README.md @@ -45,6 +45,7 @@ You must define both a **command** and a **cluster**. The command represents the user: my-snowflake-user database: MY_DB warehouse: MY_WAREHOUSE + role: MY_ROLE private_key: /etc/keys/snowflake-private-key.p8 tags: - type:snowflake From 0a3592ea4529368c09acc3ecf32da2d52c862c71 Mon Sep 17 00:00:00 2001 From: amol-patel <121927948+amol-patel@users.noreply.github.com> Date: Mon, 22 Dec 2025 11:43:51 +0530 Subject: [PATCH 2/3] Update Snowflake README to clarify optional role field --- plugins/snowflake/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/snowflake/README.md b/plugins/snowflake/README.md index 97b2c77..3344607 100644 --- a/plugins/snowflake/README.md +++ b/plugins/snowflake/README.md @@ -52,6 +52,8 @@ You must define both a **command** and a **cluster**. The command represents the - data:prod ``` +> The `role` field is optional. If not specified, Snowflake will use the default role assigned to the user. + > The `private_key` field must point to a valid **PKCS#8** PEM-formatted file accessible from the execution environment. --- From e55dc487ed363cd35cdfe9927c75f80de5c09f19 Mon Sep 17 00:00:00 2001 From: amol-patel <121927948+amol-patel@users.noreply.github.com> Date: Tue, 23 Dec 2025 17:47:49 +0530 Subject: [PATCH 3/3] Enhance Snowflake command context with role configuration - Fixed typo in error message for invalid key type. - Added 'Role' field to commandContext struct for command-level role configuration. - Updated New function to parse command context from YAML config. - Revised README to clarify role configuration and its separation from cluster settings. --- .../pkg/object/command/snowflake/snowflake.go | 22 ++++++++++++++----- plugins/snowflake/README.md | 20 ++++++++++++++--- plugins/snowflake/snowflake.go | 4 ++-- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/internal/pkg/object/command/snowflake/snowflake.go b/internal/pkg/object/command/snowflake/snowflake.go index 71d5a08..9fb4be3 100644 --- a/internal/pkg/object/command/snowflake/snowflake.go +++ b/internal/pkg/object/command/snowflake/snowflake.go @@ -25,10 +25,14 @@ const ( var ( ErrFailedToDecodePEMBlock = fmt.Errorf(`failed to decode PEM block`) - ErrInvalidKeyType = fmt.Errorf(`invalida key type`) + ErrInvalidKeyType = fmt.Errorf(`invalid key type`) ) -type commandContext struct{} +// commandContext represents command-level configuration from the YAML config file. +// Role is defined here for security - users cannot override +type commandContext struct { + Role string `yaml:"role,omitempty" json:"role,omitempty"` +} type jobContext struct { Query string `yaml:"query,omitempty" json:"query,omitempty"` @@ -39,7 +43,6 @@ type clusterContext struct { User string `yaml:"user,omitempty" json:"user,omitempty"` Database string `yaml:"database,omitempty" json:"database,omitempty"` Warehouse string `yaml:"warehouse,omitempty" json:"warehouse,omitempty"` - Role string `yaml:"role,omitempty" json:"role,omitempty"` PrivateKey string `yaml:"private_key,omitempty" json:"private_key,omitempty"` } @@ -67,8 +70,16 @@ func parsePrivateKey(privateKeyBytes []byte) (*rsa.PrivateKey, error) { } -func New(_ *heimdallContext.Context) (plugin.Handler, error) { +func New(cmdCtx *heimdallContext.Context) (plugin.Handler, error) { s := &commandContext{} + + // Parse command context from YAML config (contains role configuration) + if cmdCtx != nil { + if err := cmdCtx.Unmarshal(s); err != nil { + return nil, err + } + } + return s.handler, nil } @@ -99,12 +110,13 @@ func (s *commandContext) handler(ctx context.Context, r *plugin.Runtime, j *job. return err } + // s.Role from command context; empty string = Snowflake uses user's default role dsn, err := sf.DSN(&sf.Config{ Account: clusterContext.Account, User: clusterContext.User, Database: clusterContext.Database, Warehouse: clusterContext.Warehouse, - Role: clusterContext.Role, + Role: s.Role, Authenticator: sf.AuthTypeJwt, PrivateKey: privateKey, Application: r.UserAgent, diff --git a/plugins/snowflake/README.md b/plugins/snowflake/README.md index 3344607..b2036c7 100644 --- a/plugins/snowflake/README.md +++ b/plugins/snowflake/README.md @@ -27,6 +27,8 @@ You must define both a **command** and a **cluster**. The command represents the plugin: snowflake version: 0.0.1 description: Query user metrics from Snowflake + context: + role: DATA_ENGINEER tags: - type:snowflake cluster_tags: @@ -35,6 +37,9 @@ You must define both a **command** and a **cluster**. The command represents the ### 🔸 Cluster Configuration +The cluster configuration represents **identity** (who/where), not **permissions** (what they can do). +Role is intentionally excluded to prevent configuration duplication across clusters. + ```yaml - name: snowflake-prod-cluster status: active @@ -45,15 +50,12 @@ You must define both a **command** and a **cluster**. The command represents the user: my-snowflake-user database: MY_DB warehouse: MY_WAREHOUSE - role: MY_ROLE private_key: /etc/keys/snowflake-private-key.p8 tags: - type:snowflake - data:prod ``` -> The `role` field is optional. If not specified, Snowflake will use the default role assigned to the user. - > The `private_key` field must point to a valid **PKCS#8** PEM-formatted file accessible from the execution environment. --- @@ -80,6 +82,18 @@ Jobs must include a single SQL statement via the `context.query` field. --- +## 🎭 Role Configuration + +The Snowflake role determines what permissions the connection has. Role is **decoupled from cluster configuration** and defined at the **command level** for security: +- Prevent configuration duplication across clusters +- **Prevent per-job role escalation** - users cannot override roles in job submissions + +### ⚠️ Default Behavior + +If no role is specified in the command context, Snowflake uses the **user's default role**. + +--- + ## 📊 Returning Query Results If your query returns data, Heimdall captures it as structured output accessible via: diff --git a/plugins/snowflake/snowflake.go b/plugins/snowflake/snowflake.go index 8dfce18..9a6f96c 100644 --- a/plugins/snowflake/snowflake.go +++ b/plugins/snowflake/snowflake.go @@ -6,6 +6,6 @@ import ( "github.com/patterninc/heimdall/pkg/plugin" ) -func New(_ *context.Context) (plugin.Handler, error) { - return snowflake.New(nil) +func New(ctx *context.Context) (plugin.Handler, error) { + return snowflake.New(ctx) }