Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(snowflake): support oauth authentication #236

Merged
merged 9 commits into from
Jan 15, 2025
Merged

Conversation

shekhar-rudder
Copy link
Member

Description

This PR adds support for oauth authentication is snowflake.

  • For backward compatibility, the default value of Authenticator is AuthTypeSnowflake. If the client provides its value, it will be overridden
  • Added Host and Token fields in snowflake config

Linear Ticket

< Replace_with_Linear_Link >

Security

  • The code changed/added as part of this pull request won't create any security issues with how the software is being used.

Copy link

codecov bot commented Dec 13, 2024

Codecov Report

Attention: Patch coverage is 57.14286% with 3 lines in your changes missing coverage. Please review.

Project coverage is 85.56%. Comparing base (c9a6308) to head (9f90552).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
sqlconnect/internal/snowflake/config.go 57.14% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #236      +/-   ##
==========================================
- Coverage   85.62%   85.56%   -0.07%     
==========================================
  Files          88       88              
  Lines        4571     4579       +8     
==========================================
+ Hits         3914     3918       +4     
- Misses        499      502       +3     
- Partials      158      159       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines 45 to 51
fmt.Println("sqlconnect: Account: " + c.Account)
fmt.Println("sqlconnect: Region: " + c.Region)
fmt.Println("sqlconnect: Token: " + c.OAuthToken)
fmt.Println("sqlconnect: Warehouse: " + c.Warehouse)
fmt.Println("sqlconnect: Schema: " + c.Schema)
fmt.Println("sqlconnect: Host: " + c.Host)
fmt.Println("sqlconnect: DBName: " + c.DBName)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented logs.

Copy link
Collaborator

@atzoum atzoum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I would expect to see

package snowflake

import (
	"crypto/rsa"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/snowflakedb/gosnowflake"
	"github.com/youmark/pkcs8"
)

type Config struct {
	Account   string `json:"account"`
	Warehouse string `json:"warehouse"`
	DBName    string `json:"dbname"`
	User      string `json:"user"`
	Schema    string `json:"schema"`
	Role      string `json:"role"`
	Region    string `json:"region"`

	Protocol string `json:"protocol"` // http or https (optional)
	Host     string `json:"host"`     // hostname (optional)
	Port     int    `json:"port"`     // port (optional)

	Password string `json:"password"`

	UseKeyPairAuth       bool   `json:"useKeyPairAuth"`
	PrivateKey           string `json:"privateKey"`
	PrivateKeyPassphrase string `json:"privateKeyPassphrase"`

	UseOAuth   bool   `json:"useOAuth"`
	OAuthToken string `json:"oauthToken"`

	Application string `json:"application"`

	LoginTimeout time.Duration `json:"loginTimeout"` // default: 5m

	KeepSessionAlive  bool   `json:"keepSessionAlive"`
	UseLegacyMappings bool   `json:"useLegacyMappings"`
	QueryTag          string `json:"queryTag"`
}

func (c Config) ConnectionString() (dsn string, err error) {
	sc := gosnowflake.Config{
		Authenticator: gosnowflake.AuthTypeSnowflake,
		User:          c.User,
		Password:      c.Password,
		Account:       c.Account,
		Database:      c.DBName,
		Warehouse:     c.Warehouse,
		Schema:        c.Schema,
		Role:          c.Role,
		Region:        c.Region,
		Protocol:      c.Protocol,
		Host:          c.Host,
		Port:          c.Port,
		Application:   c.Application,
		LoginTimeout:  c.LoginTimeout,
		Params:        make(map[string]*string),
	}

	if c.UseKeyPairAuth {
		sc.Authenticator = gosnowflake.AuthTypeJwt
		privateKey, err := c.ParsePrivateKey()
		if err != nil {
			return "", fmt.Errorf("parsing private key: %w", err)
		}
		sc.PrivateKey = privateKey
	} else if c.UseOAuth {
		sc.Authenticator = gosnowflake.AuthTypeOAuth
		sc.Token = c.OAuthToken
	}

	if c.KeepSessionAlive {
		valueTrue := "true"
		sc.Params["client_session_keep_alive"] = &valueTrue
	}

	if c.QueryTag != "" {
		sc.Params["query_tag"] = &c.QueryTag
	}

	dsn, err = gosnowflake.DSN(&sc)
	if err != nil {
		err = fmt.Errorf("creating dsn: %v", err)
	}
	return
}

func (c *Config) Parse(configJSON json.RawMessage) error {
	return json.Unmarshal(configJSON, c)
}

func (c *Config) ParsePrivateKey() (*rsa.PrivateKey, error) {
	block, _ := pem.Decode([]byte(normalisePem(c.PrivateKey)))
	if block == nil {
		return nil, errors.New("decoding private key failed")
	}

	var opts [][]byte
	if len(c.PrivateKeyPassphrase) > 0 {
		opts = append(opts, []byte(c.PrivateKeyPassphrase))
	}

	rsaPrivateKey, err := pkcs8.ParsePKCS8PrivateKeyRSA(block.Bytes, opts...)
	if err != nil {
		return nil, fmt.Errorf("parsing private key: %w", err)
	}
	return rsaPrivateKey, nil
}

// normalisePem formats the content of certificates and keys by adding necessary newlines around specific markers.
func normalisePem(content string) string {
	// Remove all existing newline characters and replace them with a space
	formattedContent := strings.ReplaceAll(content, "\n", " ")

	// Add a newline after specific BEGIN markers
	formattedContent = strings.Replace(formattedContent, "-----BEGIN CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n", 1)
	formattedContent = strings.Replace(formattedContent, "-----BEGIN RSA PRIVATE KEY-----", "-----BEGIN RSA PRIVATE KEY-----\n", 1)
	formattedContent = strings.Replace(formattedContent, "-----BEGIN ENCRYPTED PRIVATE KEY-----", "-----BEGIN ENCRYPTED PRIVATE KEY-----\n", 1)
	formattedContent = strings.Replace(formattedContent, "-----BEGIN PRIVATE KEY-----", "-----BEGIN PRIVATE KEY-----\n", 1)

	// Add a newline before and after specific END markers
	formattedContent = strings.Replace(formattedContent, "-----END CERTIFICATE-----", "\n-----END CERTIFICATE-----\n", 1)
	formattedContent = strings.Replace(formattedContent, "-----END RSA PRIVATE KEY-----", "\n-----END RSA PRIVATE KEY-----\n", 1)
	formattedContent = strings.Replace(formattedContent, "-----END ENCRYPTED PRIVATE KEY-----", "\n-----END ENCRYPTED PRIVATE KEY-----\n", 1)
	formattedContent = strings.Replace(formattedContent, "-----END PRIVATE KEY-----", "\n-----END PRIVATE KEY-----\n", 1)

	return formattedContent
}

sqlconnect/internal/snowflake/config.go Outdated Show resolved Hide resolved
sqlconnect/internal/snowflake/config.go Outdated Show resolved Hide resolved
sqlconnect/internal/snowflake/config.go Outdated Show resolved Hide resolved
sqlconnect/internal/snowflake/config.go Outdated Show resolved Hide resolved
sqlconnect/internal/snowflake/config.go Outdated Show resolved Hide resolved
@arnab-p arnab-p requested a review from atzoum January 9, 2025 12:38
sqlconnect/internal/snowflake/config.go Outdated Show resolved Hide resolved
sqlconnect/internal/snowflake/config.go Outdated Show resolved Hide resolved
@arnab-p arnab-p requested a review from atzoum January 15, 2025 10:40
@arnab-p arnab-p merged commit 9bc7188 into main Jan 15, 2025
22 checks passed
@arnab-p arnab-p deleted the pro-3896-sf-oauth branch January 15, 2025 13:07
arnab-p pushed a commit that referenced this pull request Jan 15, 2025
🤖 I have created a release *beep* *boop*
---


##
[1.15.0](v1.14.0...v1.15.0)
(2025-01-15)


### Features

* **redshift:** bump github.com/aws/aws-sdk-go-v2/service/redshiftdata
from 1.30.1 to 1.31.6
([#256](#256))
([651a562](651a562))
* **snowflake:** support oauth authentication
([#236](#236))
([9bc7188](9bc7188))


### Miscellaneous

* **deps:** bump cloud.google.com/go from 0.116.0 to 0.118.0
([#254](#254))
([f1c7235](f1c7235))
* **deps:** bump github.com/aws/aws-sdk-go-v2 from 1.32.7 to 1.32.8
([#252](#252))
([c9a6308](c9a6308))
* **deps:** bump github.com/aws/aws-sdk-go-v2/config from 1.28.1 to
1.28.7 ([#249](#249))
([e0a5f5e](e0a5f5e))
* **deps:** bump github.com/gliderlabs/ssh from 0.3.7 to 0.3.8
([#243](#243))
([78799cb](78799cb))
* **deps:** bump github.com/stretchr/testify from 1.9.0 to 1.10.0
([#244](#244))
([11a4261](11a4261))
* **deps:** bump golang.org/x/crypto from 0.31.0 to 0.32.0
([#253](#253))
([e7ddbd9](e7ddbd9))
* **deps:** bump golang.org/x/net from 0.31.0 to 0.33.0 in the
go_modules group
([#251](#251))
([7132109](7132109))
* **deps:** bump google.golang.org/api from 0.210.0 to 0.216.0
([#255](#255))
([76d0416](76d0416))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants