Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions internal/pkg/object/command/snowflake/snowflake.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -66,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
}

Expand Down Expand Up @@ -98,11 +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: s.Role,
Authenticator: sf.AuthTypeJwt,
PrivateKey: privateKey,
Application: r.UserAgent,
Expand Down
17 changes: 17 additions & 0 deletions plugins/snowflake/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -77,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:
Expand Down
4 changes: 2 additions & 2 deletions plugins/snowflake/snowflake.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Loading