From 03c97c40af16e09c893ec1380909a2db2774822a Mon Sep 17 00:00:00 2001 From: Kevin Lockwood Date: Wed, 18 Jun 2025 15:30:03 -0700 Subject: [PATCH 1/4] [Low] Patch telegraf for CVE-2025-29923 --- SPECS/telegraf/CVE-2025-29923.patch | 278 ++++++++++++++++++++++++++++ SPECS/telegraf/telegraf.spec | 6 +- 2 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 SPECS/telegraf/CVE-2025-29923.patch diff --git a/SPECS/telegraf/CVE-2025-29923.patch b/SPECS/telegraf/CVE-2025-29923.patch new file mode 100644 index 00000000000..c9f7529d625 --- /dev/null +++ b/SPECS/telegraf/CVE-2025-29923.patch @@ -0,0 +1,278 @@ +From d38747aaed54482acf9f113ca0dd7621db4e901e Mon Sep 17 00:00:00 2001 +From: Kevin Lockwood +Date: Wed, 18 Jun 2025 15:02:24 -0700 +Subject: [PATCH] [Low] Patch telegraf for CVE-2025-29923 + +Upstream Patch Link: https://github.com/redis/go-redis/commit/d236865b0cfa1b752ea4b7da666b1fdcd0acebb6.patch +--- + vendor/github.com/redis/go-redis/v9/README.md | 8 ++++--- + .../github.com/redis/go-redis/v9/options.go | 11 ++++++++- + .../redis/go-redis/v9/osscluster.go | 18 ++++++++++++--- + vendor/github.com/redis/go-redis/v9/redis.go | 8 +++++-- + vendor/github.com/redis/go-redis/v9/ring.go | 15 ++++++++++-- + .../github.com/redis/go-redis/v9/sentinel.go | 23 +++++++++++++++---- + .../github.com/redis/go-redis/v9/universal.go | 21 +++++++++++++---- + 7 files changed, 85 insertions(+), 19 deletions(-) + +diff --git a/vendor/github.com/redis/go-redis/v9/README.md b/vendor/github.com/redis/go-redis/v9/README.md +index 043d3f0e..9adc7f12 100644 +--- a/vendor/github.com/redis/go-redis/v9/README.md ++++ b/vendor/github.com/redis/go-redis/v9/README.md +@@ -172,16 +172,18 @@ By default, go-redis automatically sends the client library name and version dur + + #### Disabling Identity Verification + +-When connection identity verification is not required or needs to be explicitly disabled, a `DisableIndentity` configuration option exists. In V10 of this library, `DisableIndentity` will become `DisableIdentity` in order to fix the associated typo. ++When connection identity verification is not required or needs to be explicitly disabled, a `DisableIdentity` configuration option exists. ++Initially there was a typo and the option was named `DisableIndentity` instead of `DisableIdentity`. The misspelled option is marked as Deprecated and will be removed in V10 of this library. ++Although both options will work at the moment, the correct option is `DisableIdentity`. The deprecated option will be removed in V10 of this library, so please use the correct option name to avoid any issues. + +-To disable verification, set the `DisableIndentity` option to `true` in the Redis client options: ++To disable verification, set the `DisableIdentity` option to `true` in the Redis client options: + + ```go + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", + DB: 0, +- DisableIndentity: true, // Disable set-info on connect ++ DisableIdentity: true, // Disable set-info on connect + }) + ``` + +diff --git a/vendor/github.com/redis/go-redis/v9/options.go b/vendor/github.com/redis/go-redis/v9/options.go +index dff52ae8..da9a5f99 100644 +--- a/vendor/github.com/redis/go-redis/v9/options.go ++++ b/vendor/github.com/redis/go-redis/v9/options.go +@@ -142,9 +142,18 @@ type Options struct { + // Enables read only queries on slave/follower nodes. + readOnly bool + +- // Disable set-lib on connect. Default is false. ++ // DisableIndentity - Disable set-lib on connect. ++ // ++ // default: false ++ // ++ // Deprecated: Use DisableIdentity instead. + DisableIndentity bool + ++ // DisableIdentity is used to disable CLIENT SETINFO command on connect. ++ // ++ // default: false ++ DisableIdentity bool ++ + // Add suffix to client name. Default is empty. + IdentitySuffix string + } +diff --git a/vendor/github.com/redis/go-redis/v9/osscluster.go b/vendor/github.com/redis/go-redis/v9/osscluster.go +index 17f98d9d..3d490ec1 100644 +--- a/vendor/github.com/redis/go-redis/v9/osscluster.go ++++ b/vendor/github.com/redis/go-redis/v9/osscluster.go +@@ -85,8 +85,19 @@ type ClusterOptions struct { + ConnMaxIdleTime time.Duration + ConnMaxLifetime time.Duration + +- TLSConfig *tls.Config +- DisableIndentity bool // Disable set-lib on connect. Default is false. ++ TLSConfig *tls.Config ++ ++ // DisableIndentity - Disable set-lib on connect. ++ // ++ // default: false ++ // ++ // Deprecated: Use DisableIdentity instead. ++ DisableIndentity bool ++ ++ // DisableIdentity is used to disable CLIENT SETINFO command on connect. ++ // ++ // default: false ++ DisableIdentity bool + + IdentitySuffix string // Add suffix to client name. Default is empty. + } +@@ -294,7 +305,8 @@ func (opt *ClusterOptions) clientOptions() *Options { + MaxActiveConns: opt.MaxActiveConns, + ConnMaxIdleTime: opt.ConnMaxIdleTime, + ConnMaxLifetime: opt.ConnMaxLifetime, +- DisableIndentity: opt.DisableIndentity, ++ DisableIdentity: opt.DisableIdentity, ++ DisableIndentity: opt.DisableIdentity, + IdentitySuffix: opt.IdentitySuffix, + TLSConfig: opt.TLSConfig, + // If ClusterSlots is populated, then we probably have an artificial +diff --git a/vendor/github.com/redis/go-redis/v9/redis.go b/vendor/github.com/redis/go-redis/v9/redis.go +index d25a0d31..6d38ceb6 100644 +--- a/vendor/github.com/redis/go-redis/v9/redis.go ++++ b/vendor/github.com/redis/go-redis/v9/redis.go +@@ -340,7 +340,7 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error { + return err + } + +- if !c.opt.DisableIndentity { ++ if !c.opt.DisableIdentity && !c.opt.DisableIndentity { + libName := "" + libVer := Version() + if c.opt.IdentitySuffix != "" { +@@ -349,7 +349,11 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error { + p := conn.Pipeline() + p.ClientSetInfo(ctx, WithLibraryName(libName)) + p.ClientSetInfo(ctx, WithLibraryVersion(libVer)) +- _, _ = p.Exec(ctx) ++ // Handle network errors (e.g. timeouts) in CLIENT SETINFO to avoid ++ // out of order responses later on. ++ if _, err = p.Exec(ctx); err != nil && !isRedisError(err) { ++ return err ++ } + } + + if c.opt.OnConnect != nil { +diff --git a/vendor/github.com/redis/go-redis/v9/ring.go b/vendor/github.com/redis/go-redis/v9/ring.go +index 4ae00542..b3baa11d 100644 +--- a/vendor/github.com/redis/go-redis/v9/ring.go ++++ b/vendor/github.com/redis/go-redis/v9/ring.go +@@ -98,8 +98,18 @@ type RingOptions struct { + TLSConfig *tls.Config + Limiter Limiter + ++ // DisableIndentity - Disable set-lib on connect. ++ // ++ // default: false ++ // ++ // Deprecated: Use DisableIdentity instead. + DisableIndentity bool +- IdentitySuffix string ++ ++ // DisableIdentity is used to disable CLIENT SETINFO command on connect. ++ // ++ // default: false ++ DisableIdentity bool ++ IdentitySuffix string + } + + func (opt *RingOptions) init() { +@@ -166,7 +176,8 @@ func (opt *RingOptions) clientOptions() *Options { + TLSConfig: opt.TLSConfig, + Limiter: opt.Limiter, + +- DisableIndentity: opt.DisableIndentity, ++ DisableIdentity: opt.DisableIdentity, ++ + IdentitySuffix: opt.IdentitySuffix, + } + } +diff --git a/vendor/github.com/redis/go-redis/v9/sentinel.go b/vendor/github.com/redis/go-redis/v9/sentinel.go +index 188f8849..6c082f0b 100644 +--- a/vendor/github.com/redis/go-redis/v9/sentinel.go ++++ b/vendor/github.com/redis/go-redis/v9/sentinel.go +@@ -80,8 +80,19 @@ type FailoverOptions struct { + + TLSConfig *tls.Config + ++ // DisableIndentity - Disable set-lib on connect. ++ // ++ // default: false ++ // ++ // Deprecated: Use DisableIdentity instead. + DisableIndentity bool +- IdentitySuffix string ++ ++ // DisableIdentity is used to disable CLIENT SETINFO command on connect. ++ // ++ // default: false ++ DisableIdentity bool ++ ++ IdentitySuffix string + } + + func (opt *FailoverOptions) clientOptions() *Options { +@@ -117,7 +128,8 @@ func (opt *FailoverOptions) clientOptions() *Options { + + TLSConfig: opt.TLSConfig, + +- DisableIndentity: opt.DisableIndentity, ++ DisableIdentity: opt.DisableIdentity, ++ + IdentitySuffix: opt.IdentitySuffix, + } + } +@@ -154,7 +166,8 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options { + + TLSConfig: opt.TLSConfig, + +- DisableIndentity: opt.DisableIndentity, ++ DisableIdentity: opt.DisableIdentity, ++ + IdentitySuffix: opt.IdentitySuffix, + } + } +@@ -194,8 +207,10 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions { + + TLSConfig: opt.TLSConfig, + ++ DisableIdentity: opt.DisableIdentity, + DisableIndentity: opt.DisableIndentity, +- IdentitySuffix: opt.IdentitySuffix, ++ ++ IdentitySuffix: opt.IdentitySuffix, + } + } + +diff --git a/vendor/github.com/redis/go-redis/v9/universal.go b/vendor/github.com/redis/go-redis/v9/universal.go +index 275bef3d..30aae0bc 100644 +--- a/vendor/github.com/redis/go-redis/v9/universal.go ++++ b/vendor/github.com/redis/go-redis/v9/universal.go +@@ -61,13 +61,23 @@ type UniversalOptions struct { + RouteByLatency bool + RouteRandomly bool + +- // The sentinel master name. +- // Only failover clients. +- ++ // MasterName is the sentinel master name. ++ // Only for failover clients. + MasterName string + ++ // DisableIndentity - Disable set-lib on connect. ++ // ++ // default: false ++ // ++ // Deprecated: Use DisableIdentity instead. + DisableIndentity bool +- IdentitySuffix string ++ ++ // DisableIdentity is used to disable CLIENT SETINFO command on connect. ++ // ++ // default: false ++ DisableIdentity bool ++ ++ IdentitySuffix string + } + + // Cluster returns cluster options created from the universal options. +@@ -112,6 +122,7 @@ func (o *UniversalOptions) Cluster() *ClusterOptions { + + TLSConfig: o.TLSConfig, + ++ DisableIdentity: o.DisableIdentity, + DisableIndentity: o.DisableIndentity, + IdentitySuffix: o.IdentitySuffix, + } +@@ -158,6 +169,7 @@ func (o *UniversalOptions) Failover() *FailoverOptions { + + TLSConfig: o.TLSConfig, + ++ DisableIdentity: o.DisableIdentity, + DisableIndentity: o.DisableIndentity, + IdentitySuffix: o.IdentitySuffix, + } +@@ -201,6 +213,7 @@ func (o *UniversalOptions) Simple() *Options { + + TLSConfig: o.TLSConfig, + ++ DisableIdentity: o.DisableIdentity, + DisableIndentity: o.DisableIndentity, + IdentitySuffix: o.IdentitySuffix, + } +-- +2.34.1 + diff --git a/SPECS/telegraf/telegraf.spec b/SPECS/telegraf/telegraf.spec index 4af7ea5e0b2..a6ccc303308 100644 --- a/SPECS/telegraf/telegraf.spec +++ b/SPECS/telegraf/telegraf.spec @@ -1,7 +1,7 @@ Summary: agent for collecting, processing, aggregating, and writing metrics. Name: telegraf Version: 1.31.0 -Release: 10%{?dist} +Release: 11%{?dist} License: MIT Vendor: Microsoft Corporation Distribution: Azure Linux @@ -23,6 +23,7 @@ Patch8: CVE-2025-30204.patch Patch9: CVE-2025-27144.patch Patch10: CVE-2025-30215.patch Patch11: CVE-2025-22872.patch +Patch12: CVE-2025-29923.patch BuildRequires: golang BuildRequires: systemd-devel @@ -87,6 +88,9 @@ fi %dir %{_sysconfdir}/%{name}/telegraf.d %changelog +* Wed Jun 18 2025 Kevin Lockwood - 1.31.0-11 +- Patch CVE-2025-29923 + * Tue Apr 22 2025 Mayank Singh - 1.31.0-10 - Fix CVE-2025-22872 with an upstream patch From f61c1cda29b5ab01605a76b985c58869158a473d Mon Sep 17 00:00:00 2001 From: Kevin Lockwood Date: Tue, 24 Jun 2025 14:20:44 -0700 Subject: [PATCH 2/4] Add mistakenly removed `DisableIndentity` references --- SPECS/telegraf/CVE-2025-29923.patch | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/SPECS/telegraf/CVE-2025-29923.patch b/SPECS/telegraf/CVE-2025-29923.patch index c9f7529d625..3e85547e9e1 100644 --- a/SPECS/telegraf/CVE-2025-29923.patch +++ b/SPECS/telegraf/CVE-2025-29923.patch @@ -1,4 +1,4 @@ -From d38747aaed54482acf9f113ca0dd7621db4e901e Mon Sep 17 00:00:00 2001 +From 6e3449cdfdf88d31d6ef5a9b7a8ac5e61c3b3ece Mon Sep 17 00:00:00 2001 From: Kevin Lockwood Date: Wed, 18 Jun 2025 15:02:24 -0700 Subject: [PATCH] [Low] Patch telegraf for CVE-2025-29923 @@ -6,13 +6,13 @@ Subject: [PATCH] [Low] Patch telegraf for CVE-2025-29923 Upstream Patch Link: https://github.com/redis/go-redis/commit/d236865b0cfa1b752ea4b7da666b1fdcd0acebb6.patch --- vendor/github.com/redis/go-redis/v9/README.md | 8 ++++--- - .../github.com/redis/go-redis/v9/options.go | 11 ++++++++- - .../redis/go-redis/v9/osscluster.go | 18 ++++++++++++--- + .../github.com/redis/go-redis/v9/options.go | 11 +++++++++- + .../redis/go-redis/v9/osscluster.go | 18 +++++++++++++--- vendor/github.com/redis/go-redis/v9/redis.go | 8 +++++-- - vendor/github.com/redis/go-redis/v9/ring.go | 15 ++++++++++-- - .../github.com/redis/go-redis/v9/sentinel.go | 23 +++++++++++++++---- - .../github.com/redis/go-redis/v9/universal.go | 21 +++++++++++++---- - 7 files changed, 85 insertions(+), 19 deletions(-) + vendor/github.com/redis/go-redis/v9/ring.go | 14 ++++++++++++- + .../github.com/redis/go-redis/v9/sentinel.go | 21 +++++++++++++++++-- + .../github.com/redis/go-redis/v9/universal.go | 21 +++++++++++++++---- + 7 files changed, 85 insertions(+), 16 deletions(-) diff --git a/vendor/github.com/redis/go-redis/v9/README.md b/vendor/github.com/redis/go-redis/v9/README.md index 043d3f0e..9adc7f12 100644 @@ -127,7 +127,7 @@ index d25a0d31..6d38ceb6 100644 if c.opt.OnConnect != nil { diff --git a/vendor/github.com/redis/go-redis/v9/ring.go b/vendor/github.com/redis/go-redis/v9/ring.go -index 4ae00542..b3baa11d 100644 +index 4ae00542..e9bd6595 100644 --- a/vendor/github.com/redis/go-redis/v9/ring.go +++ b/vendor/github.com/redis/go-redis/v9/ring.go @@ -98,8 +98,18 @@ type RingOptions struct { @@ -150,18 +150,18 @@ index 4ae00542..b3baa11d 100644 } func (opt *RingOptions) init() { -@@ -166,7 +176,8 @@ func (opt *RingOptions) clientOptions() *Options { +@@ -166,7 +176,9 @@ func (opt *RingOptions) clientOptions() *Options { TLSConfig: opt.TLSConfig, Limiter: opt.Limiter, -- DisableIndentity: opt.DisableIndentity, + DisableIdentity: opt.DisableIdentity, + DisableIndentity: opt.DisableIndentity, + IdentitySuffix: opt.IdentitySuffix, } } diff --git a/vendor/github.com/redis/go-redis/v9/sentinel.go b/vendor/github.com/redis/go-redis/v9/sentinel.go -index 188f8849..6c082f0b 100644 +index 188f8849..72dc265a 100644 --- a/vendor/github.com/redis/go-redis/v9/sentinel.go +++ b/vendor/github.com/redis/go-redis/v9/sentinel.go @@ -80,8 +80,19 @@ type FailoverOptions struct { @@ -185,27 +185,27 @@ index 188f8849..6c082f0b 100644 } func (opt *FailoverOptions) clientOptions() *Options { -@@ -117,7 +128,8 @@ func (opt *FailoverOptions) clientOptions() *Options { +@@ -117,7 +128,9 @@ func (opt *FailoverOptions) clientOptions() *Options { TLSConfig: opt.TLSConfig, -- DisableIndentity: opt.DisableIndentity, + DisableIdentity: opt.DisableIdentity, + DisableIndentity: opt.DisableIndentity, + IdentitySuffix: opt.IdentitySuffix, } } -@@ -154,7 +166,8 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options { +@@ -154,7 +167,9 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options { TLSConfig: opt.TLSConfig, -- DisableIndentity: opt.DisableIndentity, + DisableIdentity: opt.DisableIdentity, + DisableIndentity: opt.DisableIndentity, + IdentitySuffix: opt.IdentitySuffix, } } -@@ -194,8 +207,10 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions { +@@ -194,8 +209,10 @@ func (opt *FailoverOptions) clusterOptions() *ClusterOptions { TLSConfig: opt.TLSConfig, From c5935326969bc1aa2a9c6578e997fd7f25da548d Mon Sep 17 00:00:00 2001 From: Kevin Lockwood Date: Thu, 26 Jun 2025 13:58:13 -0700 Subject: [PATCH 3/4] [Low] Patch telegraf for CVE-2025-46327 --- SPECS/telegraf/CVE-2025-46327.patch | 1854 +++++++++++++++++++++++++++ SPECS/telegraf/telegraf.spec | 4 +- 2 files changed, 1857 insertions(+), 1 deletion(-) create mode 100644 SPECS/telegraf/CVE-2025-46327.patch diff --git a/SPECS/telegraf/CVE-2025-46327.patch b/SPECS/telegraf/CVE-2025-46327.patch new file mode 100644 index 00000000000..6998ae5eaa6 --- /dev/null +++ b/SPECS/telegraf/CVE-2025-46327.patch @@ -0,0 +1,1854 @@ +From 2f9da773e6e0ea2e7653b0a379304c09e4422a0a Mon Sep 17 00:00:00 2001 +From: Kevin Lockwood +Date: Wed, 25 Jun 2025 16:38:08 -0700 +Subject: [PATCH 1/5] SNOW-1825790 Separate secure storage managers based on + file and keyring (#1295) + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/e926883b12016eb30ae25c2027f2cf70714eb2c5.patch +--- + .../snowflakedb/gosnowflake/auth.go | 12 +- + .../gosnowflake/secure_storage_manager.go | 384 ++++++++++-------- + 2 files changed, 230 insertions(+), 166 deletions(-) + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/auth.go b/vendor/github.com/snowflakedb/gosnowflake/auth.go +index 6d069a91..888cd1ba 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/auth.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/auth.go +@@ -364,10 +364,10 @@ func authenticate( + logger.WithContext(ctx).Errorln("Authentication FAILED") + sc.rest.TokenAccessor.SetTokens("", "", -1) + if sessionParameters[clientRequestMfaToken] == true { +- deleteCredential(sc, mfaToken) ++ credentialsStorage.deleteCredential(sc, mfaToken) + } + if sessionParameters[clientStoreTemporaryCredential] == true { +- deleteCredential(sc, idToken) ++ credentialsStorage.deleteCredential(sc, idToken) + } + code, err := strconv.Atoi(respd.Code) + if err != nil { +@@ -384,11 +384,11 @@ func authenticate( + sc.rest.TokenAccessor.SetTokens(respd.Data.Token, respd.Data.MasterToken, respd.Data.SessionID) + if sessionParameters[clientRequestMfaToken] == true { + token := respd.Data.MfaToken +- setCredential(sc, mfaToken, token) ++ credentialsStorage.setCredential(sc, mfaToken, token) + } + if sessionParameters[clientStoreTemporaryCredential] == true { + token := respd.Data.IDToken +- setCredential(sc, idToken, token) ++ credentialsStorage.setCredential(sc, idToken, token) + } + return &respd.Data, nil + } +@@ -565,9 +565,9 @@ func authenticateWithConfig(sc *snowflakeConn) error { + } + + func fillCachedIDToken(sc *snowflakeConn) { +- getCredential(sc, idToken) ++ credentialsStorage.getCredential(sc, idToken) + } + + func fillCachedMfaToken(sc *snowflakeConn) { +- getCredential(sc, mfaToken) ++ credentialsStorage.getCredential(sc, mfaToken) + } +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index da8610e3..fc392662 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -4,6 +4,7 @@ package gosnowflake + + import ( + "encoding/json" ++ "fmt" + "os" + "path/filepath" + "runtime" +@@ -20,68 +21,231 @@ const ( + credCacheFileName = "temporary_credential.json" + ) + +-var ( +- credCacheDir = "" +- credCache = "" +- localCredCache = map[string]string{} +-) ++type secureStorageManager interface { ++ setCredential(sc *snowflakeConn, credType, token string) ++ getCredential(sc *snowflakeConn, credType string) ++ deleteCredential(sc *snowflakeConn, credType string) ++} + +-var ( +- credCacheLock sync.RWMutex +-) ++var credentialsStorage = newSecureStorageManager() ++ ++func newSecureStorageManager() secureStorageManager { ++ switch runtime.GOOS { ++ case "linux": ++ return newFileBasedSecureStorageManager() ++ case "darwin", "windows": ++ return newKeyringBasedSecureStorageManager() ++ default: ++ return newNoopSecureStorageManager() ++ } ++} ++ ++type fileBasedSecureStorageManager struct { ++ credCacheFilePath string ++ localCredCache map[string]string ++ credCacheLock sync.RWMutex ++} ++ ++func newFileBasedSecureStorageManager() secureStorageManager { ++ ssm := &fileBasedSecureStorageManager{ ++ localCredCache: map[string]string{}, ++ credCacheLock: sync.RWMutex{}, ++ } ++ credCacheDir := ssm.buildCredCacheDirPath() ++ if err := ssm.createCacheDir(credCacheDir); err != nil { ++ logger.Debugf("failed to create credentials cache dir. %v", err) ++ return newNoopSecureStorageManager() ++ } ++ credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) ++ logger.Infof("Credentials cache path: %v", credCacheFilePath) ++ ssm.credCacheFilePath = credCacheFilePath ++ return ssm ++} ++ ++func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { ++ _, err := os.Stat(credCacheDir) ++ if os.IsNotExist(err) { ++ if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { ++ return fmt.Errorf("failed to create cache directory. %v, err: %v", credCacheDir, err) ++ } ++ return nil ++ } ++ return err ++} ++ ++func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { ++ credCacheDir := os.Getenv(credCacheDirEnv) ++ if credCacheDir != "" { ++ return credCacheDir ++ } ++ home := os.Getenv("HOME") ++ if home == "" { ++ logger.Info("HOME is blank") ++ return "" ++ } ++ credCacheDir = filepath.Join(home, ".cache", "snowflake") ++ return credCacheDir ++} ++ ++func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { ++ if token == "" { ++ logger.Debug("no token provided") ++ } else { ++ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ ssm.credCacheLock.Lock() ++ defer ssm.credCacheLock.Unlock() ++ ssm.localCredCache[credentialsKey] = token ++ ++ j, err := json.Marshal(ssm.localCredCache) ++ if err != nil { ++ logger.Warnf("failed to convert credential to JSON.") ++ return ++ } ++ ++ logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) ++ credCacheLockFileName := ssm.credCacheFilePath + ".lck" ++ logger.Debugf("Creating lock file. %v", credCacheLockFileName) ++ err = os.Mkdir(credCacheLockFileName, 0600) + +-func createCredentialCacheDir() { +- credCacheDir = os.Getenv(credCacheDirEnv) +- if credCacheDir == "" { +- switch runtime.GOOS { +- case "windows": +- credCacheDir = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local", "Snowflake", "Caches") +- case "darwin": +- home := os.Getenv("HOME") +- if home == "" { +- logger.Info("HOME is blank.") ++ switch { ++ case os.IsExist(err): ++ statinfo, err := os.Stat(credCacheLockFileName) ++ if err != nil { ++ logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) ++ return ++ } ++ if time.Since(statinfo.ModTime()) < 15*time.Minute { ++ logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) ++ return ++ } ++ if err = os.Remove(credCacheLockFileName); err != nil { ++ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ return + } +- credCacheDir = filepath.Join(home, "Library", "Caches", "Snowflake") +- default: +- home := os.Getenv("HOME") +- if home == "" { +- logger.Info("HOME is blank") ++ if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { ++ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ return + } +- credCacheDir = filepath.Join(home, ".cache", "snowflake") ++ } ++ defer os.RemoveAll(credCacheLockFileName) ++ ++ if err = os.WriteFile(ssm.credCacheFilePath, j, 0644); err != nil { ++ logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) + } + } ++} + +- if _, err := os.Stat(credCacheDir); os.IsNotExist(err) { +- if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { +- logger.Debugf("Failed to create cache directory. %v, err: %v. ignored\n", credCacheDir, err) ++func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { ++ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ ssm.credCacheLock.Lock() ++ defer ssm.credCacheLock.Unlock() ++ localCredCache := ssm.readTemporaryCacheFile() ++ cred := localCredCache[credentialsKey] ++ if cred != "" { ++ logger.Debug("Successfully read token. Returning as string") ++ } else { ++ logger.Debug("Returned credential is empty") ++ } ++ ++ if credType == idToken { ++ sc.cfg.IDToken = cred ++ } else if credType == mfaToken { ++ sc.cfg.MfaToken = cred ++ } else { ++ logger.Debugf("Unrecognized type %v for local cached credential", credType) ++ } ++} ++ ++func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { ++ jsonData, err := os.ReadFile(ssm.credCacheFilePath) ++ if err != nil { ++ logger.Debugf("Failed to read credential file: %v", err) ++ return nil ++ } ++ err = json.Unmarshal([]byte(jsonData), &ssm.localCredCache) ++ if err != nil { ++ logger.Debugf("failed to read JSON. Err: %v", err) ++ return nil ++ } ++ ++ return ssm.localCredCache ++} ++ ++func (ssm *fileBasedSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { ++ ssm.credCacheLock.Lock() ++ defer ssm.credCacheLock.Unlock() ++ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ delete(ssm.localCredCache, credentialsKey) ++ j, err := json.Marshal(ssm.localCredCache) ++ if err != nil { ++ logger.Warnf("failed to convert credential to JSON.") ++ return ++ } ++ ssm.writeTemporaryCacheFile(j) ++} ++ ++func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) { ++ logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) ++ credCacheLockFileName := ssm.credCacheFilePath + ".lck" ++ err := os.Mkdir(credCacheLockFileName, 0600) ++ logger.Debugf("Creating lock file. %v", credCacheLockFileName) ++ ++ switch { ++ case os.IsExist(err): ++ statinfo, err := os.Stat(credCacheLockFileName) ++ if err != nil { ++ logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) ++ return ++ } ++ if time.Since(statinfo.ModTime()) < 15*time.Minute { ++ logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) ++ return + } ++ if err = os.Remove(credCacheLockFileName); err != nil { ++ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ return ++ } ++ if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { ++ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ return ++ } ++ } ++ defer os.RemoveAll(credCacheLockFileName) ++ ++ if err = os.WriteFile(ssm.credCacheFilePath, input, 0644); err != nil { ++ logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) + } +- credCache = filepath.Join(credCacheDir, credCacheFileName) +- logger.Infof("Cache directory: %v", credCache) + } + +-func setCredential(sc *snowflakeConn, credType, token string) { ++type keyringSecureStorageManager struct { ++} ++ ++func newKeyringBasedSecureStorageManager() secureStorageManager { ++ return &keyringSecureStorageManager{} ++} ++ ++func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { + if token == "" { + logger.Debug("no token provided") + } else { +- var target string ++ var credentialsKey string + if runtime.GOOS == "windows" { +- target = driverName + ":" + credType ++ credentialsKey = driverName + ":" + credType + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(sc.cfg.Host), + ServiceName: strings.ToUpper(sc.cfg.User), + }) + item := keyring.Item{ +- Key: target, ++ Key: credentialsKey, + Data: []byte(token), + } + if err := ring.Set(item); err != nil { + logger.Debugf("Failed to write to Windows credential manager. Err: %v", err) + } + } else if runtime.GOOS == "darwin" { +- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ +- ServiceName: target, ++ ServiceName: credentialsKey, + }) + account := strings.ToUpper(sc.cfg.User) + item := keyring.Item{ +@@ -91,33 +255,28 @@ func setCredential(sc *snowflakeConn, credType, token string) { + if err := ring.Set(item); err != nil { + logger.Debugf("Failed to write to keychain. Err: %v", err) + } +- } else if runtime.GOOS == "linux" { +- createCredentialCacheDir() +- writeTemporaryCredential(sc, credType, token) +- } else { +- logger.Debug("OS not supported for Local Secure Storage") + } + } + } + +-func getCredential(sc *snowflakeConn, credType string) { +- var target string ++func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { ++ var credentialsKey string + cred := "" + if runtime.GOOS == "windows" { +- target = driverName + ":" + credType ++ credentialsKey = driverName + ":" + credType + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(sc.cfg.Host), + ServiceName: strings.ToUpper(sc.cfg.User), + }) +- i, err := ring.Get(target) ++ i, err := ring.Get(credentialsKey) + if err != nil { +- logger.Debugf("Failed to read target or could not find it in Windows Credential Manager. Error: %v", err) ++ logger.Debugf("Failed to read credentialsKey or could not find it in Windows Credential Manager. Error: %v", err) + } + cred = string(i.Data) + } else if runtime.GOOS == "darwin" { +- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ +- ServiceName: target, ++ ServiceName: credentialsKey, + }) + account := strings.ToUpper(sc.cfg.User) + i, err := ring.Get(account) +@@ -130,11 +289,6 @@ func getCredential(sc *snowflakeConn, credType string) { + } else { + logger.Debug("Successfully read token. Returning as string") + } +- } else if runtime.GOOS == "linux" { +- createCredentialCacheDir() +- cred = readTemporaryCredential(sc, credType) +- } else { +- logger.Debug("OS not supported for Local Secure Storage") + } + + if credType == idToken { +@@ -146,139 +300,49 @@ func getCredential(sc *snowflakeConn, credType string) { + } + } + +-func deleteCredential(sc *snowflakeConn, credType string) { +- target := driverName + ":" + credType ++func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { ++ credentialsKey := driverName + ":" + credType + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(sc.cfg.Host), + ServiceName: strings.ToUpper(sc.cfg.User), + }) +- err := ring.Remove(target) ++ err := ring.Remove(credentialsKey) + if err != nil { +- logger.Debugf("Failed to delete target in Windows Credential Manager. Error: %v", err) ++ logger.Debugf("Failed to delete credentialsKey in Windows Credential Manager. Error: %v", err) + } + } else if runtime.GOOS == "darwin" { +- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ +- ServiceName: target, ++ ServiceName: credentialsKey, + }) + account := strings.ToUpper(sc.cfg.User) + err := ring.Remove(account) + if err != nil { +- logger.Debugf("Failed to delete target in keychain. Error: %v", err) ++ logger.Debugf("Failed to delete credentialsKey in keychain. Error: %v", err) + } +- } else if runtime.GOOS == "linux" { +- deleteTemporaryCredential(sc, credType) + } + } + +-// Reads temporary credential file when OS is Linux. +-func readTemporaryCredential(sc *snowflakeConn, credType string) string { +- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) +- credCacheLock.Lock() +- defer credCacheLock.Unlock() +- localCredCache := readTemporaryCacheFile() +- cred := localCredCache[target] +- if cred != "" { +- logger.Debug("Successfully read token. Returning as string") +- } else { +- logger.Debug("Returned credential is empty") +- } +- return cred ++func buildCredentialsKey(host, user, credType string) string { ++ host = strings.ToUpper(host) ++ user = strings.ToUpper(user) ++ credType = strings.ToUpper(credType) ++ return host + ":" + user + ":" + driverName + ":" + credType + } + +-// Writes to temporary credential file when OS is Linux. +-func writeTemporaryCredential(sc *snowflakeConn, credType, token string) { +- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) +- credCacheLock.Lock() +- defer credCacheLock.Unlock() +- localCredCache[target] = token +- +- j, err := json.Marshal(localCredCache) +- if err != nil { +- logger.Warnf("failed to convert credential to JSON.") +- return +- } +- writeTemporaryCacheFile(j) ++type noopSecureStorageManager struct { + } + +-func deleteTemporaryCredential(sc *snowflakeConn, credType string) { +- if credCacheDir == "" { +- logger.Debug("Cache file doesn't exist. Skipping deleting credential file.") +- } else { +- credCacheLock.Lock() +- defer credCacheLock.Unlock() +- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) +- delete(localCredCache, target) +- j, err := json.Marshal(localCredCache) +- if err != nil { +- logger.Warnf("failed to convert credential to JSON.") +- return +- } +- writeTemporaryCacheFile(j) +- } ++func newNoopSecureStorageManager() secureStorageManager { ++ return &noopSecureStorageManager{} + } + +-func readTemporaryCacheFile() map[string]string { +- if credCache == "" { +- logger.Debug("Cache file doesn't exist. Skipping reading credential file.") +- return nil +- } +- jsonData, err := os.ReadFile(credCache) +- if err != nil { +- logger.Debugf("Failed to read credential file: %v", err) +- return nil +- } +- err = json.Unmarshal([]byte(jsonData), &localCredCache) +- if err != nil { +- logger.Debugf("failed to read JSON. Err: %v", err) +- return nil +- } +- +- return localCredCache ++func (ssm *noopSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { + } + +-func writeTemporaryCacheFile(input []byte) { +- if credCache == "" { +- logger.Debug("Cache file doesn't exist. Skipping writing temporary credential file.") +- } else { +- logger.Debugf("writing credential cache file. %v\n", credCache) +- credCacheLockFileName := credCache + ".lck" +- err := os.Mkdir(credCacheLockFileName, 0600) +- logger.Debugf("Creating lock file. %v", credCacheLockFileName) +- +- switch { +- case os.IsExist(err): +- statinfo, err := os.Stat(credCacheLockFileName) +- if err != nil { +- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", credCache, err) +- return +- } +- if time.Since(statinfo.ModTime()) < 15*time.Minute { +- logger.Debugf("other process locks the cache file. %v. ignored.\n", credCache) +- return +- } +- if err = os.Remove(credCacheLockFileName); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return +- } +- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return +- } +- } +- defer os.RemoveAll(credCacheLockFileName) +- +- if err = os.WriteFile(credCache, input, 0644); err != nil { +- logger.Debugf("Failed to write the cache file. File: %v err: %v.", credCache, err) +- } +- } ++func (ssm *noopSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { + } + +-func convertTarget(host, user, credType string) string { +- host = strings.ToUpper(host) +- user = strings.ToUpper(user) +- credType = strings.ToUpper(credType) +- target := host + ":" + user + ":" + driverName + ":" + credType +- return target ++func (ssm *noopSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { //TODO implement me + } +-- +2.34.1 + + +From 207a4e0397e893cc8d90e3ce551d36b11f1c537d Mon Sep 17 00:00:00 2001 +From: Kevin Lockwood +Date: Wed, 25 Jun 2025 16:39:09 -0700 +Subject: [PATCH 2/5] SNOW-1825790 Token cache refactor - v2 (#1299) + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/d8df82efb12b0428a88dfe4ec80e3b0936569d64.patch +--- + .../snowflakedb/gosnowflake/auth.go | 22 +-- + .../gosnowflake/secure_storage_manager.go | 146 ++++++++++-------- + 2 files changed, 88 insertions(+), 80 deletions(-) + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/auth.go b/vendor/github.com/snowflakedb/gosnowflake/auth.go +index 888cd1ba..f2948475 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/auth.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/auth.go +@@ -25,8 +25,6 @@ const ( + ) + + const ( +- idToken = "ID_TOKEN" +- mfaToken = "MFATOKEN" + clientStoreTemporaryCredential = "CLIENT_STORE_TEMPORARY_CREDENTIAL" + clientRequestMfaToken = "CLIENT_REQUEST_MFA_TOKEN" + idTokenAuthenticator = "ID_TOKEN" +@@ -364,10 +362,10 @@ func authenticate( + logger.WithContext(ctx).Errorln("Authentication FAILED") + sc.rest.TokenAccessor.SetTokens("", "", -1) + if sessionParameters[clientRequestMfaToken] == true { +- credentialsStorage.deleteCredential(sc, mfaToken) ++ credentialsStorage.deleteCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) + } + if sessionParameters[clientStoreTemporaryCredential] == true { +- credentialsStorage.deleteCredential(sc, idToken) ++ credentialsStorage.deleteCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) + } + code, err := strconv.Atoi(respd.Code) + if err != nil { +@@ -384,11 +382,11 @@ func authenticate( + sc.rest.TokenAccessor.SetTokens(respd.Data.Token, respd.Data.MasterToken, respd.Data.SessionID) + if sessionParameters[clientRequestMfaToken] == true { + token := respd.Data.MfaToken +- credentialsStorage.setCredential(sc, mfaToken, token) ++ credentialsStorage.setCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User), token) + } + if sessionParameters[clientStoreTemporaryCredential] == true { + token := respd.Data.IDToken +- credentialsStorage.setCredential(sc, idToken, token) ++ credentialsStorage.setCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User), token) + } + return &respd.Data, nil + } +@@ -513,7 +511,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { + sc.cfg.ClientStoreTemporaryCredential = ConfigBoolTrue + } + if sc.cfg.ClientStoreTemporaryCredential == ConfigBoolTrue { +- fillCachedIDToken(sc) ++ sc.cfg.IDToken = credentialsStorage.getCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) + } + // Disable console login by default + if sc.cfg.DisableConsoleLogin == configBoolNotSet { +@@ -526,7 +524,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { + sc.cfg.ClientRequestMfaToken = ConfigBoolTrue + } + if sc.cfg.ClientRequestMfaToken == ConfigBoolTrue { +- fillCachedMfaToken(sc) ++ sc.cfg.MfaToken = credentialsStorage.getCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) + } + } + +@@ -563,11 +561,3 @@ func authenticateWithConfig(sc *snowflakeConn) error { + sc.ctx = context.WithValue(sc.ctx, SFSessionIDKey, authData.SessionID) + return nil + } +- +-func fillCachedIDToken(sc *snowflakeConn) { +- credentialsStorage.getCredential(sc, idToken) +-} +- +-func fillCachedMfaToken(sc *snowflakeConn) { +- credentialsStorage.getCredential(sc, mfaToken) +-} +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index fc392662..6be71a57 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -15,16 +15,48 @@ import ( + "github.com/99designs/keyring" + ) + ++type tokenType string ++ ++const ( ++ idToken tokenType = "ID_TOKEN" ++ mfaToken tokenType = "MFATOKEN" ++) ++ + const ( + driverName = "SNOWFLAKE-GO-DRIVER" + credCacheDirEnv = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR" + credCacheFileName = "temporary_credential.json" + ) + ++type secureTokenSpec struct { ++ host, user string ++ tokenType tokenType ++} ++ ++func (t *secureTokenSpec) buildKey() string { ++ return buildCredentialsKey(t.host, t.user, t.tokenType) ++} ++ ++func newMfaTokenSpec(host, user string) *secureTokenSpec { ++ return &secureTokenSpec{ ++ host, ++ user, ++ mfaToken, ++ } ++} ++ ++func newIDTokenSpec(host, user string) *secureTokenSpec { ++ return &secureTokenSpec{ ++ host, ++ user, ++ idToken, ++ } ++} ++ + type secureStorageManager interface { +- setCredential(sc *snowflakeConn, credType, token string) +- getCredential(sc *snowflakeConn, credType string) +- deleteCredential(sc *snowflakeConn, credType string) ++ setCredential(tokenSpec *secureTokenSpec, value string) ++ getCredential(tokenSpec *secureTokenSpec) string ++ deleteCredential(tokenSpec *secureTokenSpec) + } + + var credentialsStorage = newSecureStorageManager() +@@ -32,7 +64,12 @@ var credentialsStorage = newSecureStorageManager() + func newSecureStorageManager() secureStorageManager { + switch runtime.GOOS { + case "linux": +- return newFileBasedSecureStorageManager() ++ ssm, err := newFileBasedSecureStorageManager() ++ if err != nil { ++ logger.Debugf("failed to create credentials cache dir. %v", err) ++ return newNoopSecureStorageManager() ++ } ++ return ssm + case "darwin", "windows": + return newKeyringBasedSecureStorageManager() + default: +@@ -46,20 +83,19 @@ type fileBasedSecureStorageManager struct { + credCacheLock sync.RWMutex + } + +-func newFileBasedSecureStorageManager() secureStorageManager { ++func newFileBasedSecureStorageManager() (*fileBasedSecureStorageManager, error) { + ssm := &fileBasedSecureStorageManager{ + localCredCache: map[string]string{}, + credCacheLock: sync.RWMutex{}, + } + credCacheDir := ssm.buildCredCacheDirPath() + if err := ssm.createCacheDir(credCacheDir); err != nil { +- logger.Debugf("failed to create credentials cache dir. %v", err) +- return newNoopSecureStorageManager() ++ return nil, err + } + credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) + logger.Infof("Credentials cache path: %v", credCacheFilePath) + ssm.credCacheFilePath = credCacheFilePath +- return ssm ++ return ssm, nil + } + + func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { +@@ -87,14 +123,14 @@ func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { + return credCacheDir + } + +-func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { +- if token == "" { ++func (ssm *fileBasedSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { ++ if value == "" { + logger.Debug("no token provided") + } else { +- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey := tokenSpec.buildKey() + ssm.credCacheLock.Lock() + defer ssm.credCacheLock.Unlock() +- ssm.localCredCache[credentialsKey] = token ++ ssm.localCredCache[credentialsKey] = value + + j, err := json.Marshal(ssm.localCredCache) + if err != nil { +@@ -135,8 +171,8 @@ func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credT + } + } + +-func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { +- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++func (ssm *fileBasedSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { ++ credentialsKey := tokenSpec.buildKey() + ssm.credCacheLock.Lock() + defer ssm.credCacheLock.Unlock() + localCredCache := ssm.readTemporaryCacheFile() +@@ -146,14 +182,7 @@ func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credT + } else { + logger.Debug("Returned credential is empty") + } +- +- if credType == idToken { +- sc.cfg.IDToken = cred +- } else if credType == mfaToken { +- sc.cfg.MfaToken = cred +- } else { +- logger.Debugf("Unrecognized type %v for local cached credential", credType) +- } ++ return cred + } + + func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { +@@ -171,10 +200,10 @@ func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]st + return ssm.localCredCache + } + +-func (ssm *fileBasedSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { ++func (ssm *fileBasedSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { + ssm.credCacheLock.Lock() + defer ssm.credCacheLock.Unlock() +- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey := tokenSpec.buildKey() + delete(ssm.localCredCache, credentialsKey) + j, err := json.Marshal(ssm.localCredCache) + if err != nil { +@@ -220,37 +249,35 @@ func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) + type keyringSecureStorageManager struct { + } + +-func newKeyringBasedSecureStorageManager() secureStorageManager { ++func newKeyringBasedSecureStorageManager() *keyringSecureStorageManager { + return &keyringSecureStorageManager{} + } + +-func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { +- if token == "" { ++func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { ++ if value == "" { + logger.Debug("no token provided") + } else { +- var credentialsKey string ++ credentialsKey := tokenSpec.buildKey() + if runtime.GOOS == "windows" { +- credentialsKey = driverName + ":" + credType + ring, _ := keyring.Open(keyring.Config{ +- WinCredPrefix: strings.ToUpper(sc.cfg.Host), +- ServiceName: strings.ToUpper(sc.cfg.User), ++ WinCredPrefix: strings.ToUpper(tokenSpec.host), ++ ServiceName: strings.ToUpper(tokenSpec.user), + }) + item := keyring.Item{ + Key: credentialsKey, +- Data: []byte(token), ++ Data: []byte(value), + } + if err := ring.Set(item); err != nil { + logger.Debugf("Failed to write to Windows credential manager. Err: %v", err) + } + } else if runtime.GOOS == "darwin" { +- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ + ServiceName: credentialsKey, + }) +- account := strings.ToUpper(sc.cfg.User) ++ account := strings.ToUpper(tokenSpec.user) + item := keyring.Item{ + Key: account, +- Data: []byte(token), ++ Data: []byte(value), + } + if err := ring.Set(item); err != nil { + logger.Debugf("Failed to write to keychain. Err: %v", err) +@@ -259,14 +286,13 @@ func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credTyp + } + } + +-func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { +- var credentialsKey string ++func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { + cred := "" ++ credentialsKey := tokenSpec.buildKey() + if runtime.GOOS == "windows" { +- credentialsKey = driverName + ":" + credType + ring, _ := keyring.Open(keyring.Config{ +- WinCredPrefix: strings.ToUpper(sc.cfg.Host), +- ServiceName: strings.ToUpper(sc.cfg.User), ++ WinCredPrefix: strings.ToUpper(tokenSpec.host), ++ ServiceName: strings.ToUpper(tokenSpec.user), + }) + i, err := ring.Get(credentialsKey) + if err != nil { +@@ -274,11 +300,10 @@ func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credTyp + } + cred = string(i.Data) + } else if runtime.GOOS == "darwin" { +- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ + ServiceName: credentialsKey, + }) +- account := strings.ToUpper(sc.cfg.User) ++ account := strings.ToUpper(tokenSpec.user) + i, err := ring.Get(account) + if err != nil { + logger.Debugf("Failed to find the item in keychain or item does not exist. Error: %v", err) +@@ -290,33 +315,25 @@ func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credTyp + logger.Debug("Successfully read token. Returning as string") + } + } +- +- if credType == idToken { +- sc.cfg.IDToken = cred +- } else if credType == mfaToken { +- sc.cfg.MfaToken = cred +- } else { +- logger.Debugf("Unrecognized type %v for local cached credential", credType) +- } ++ return cred + } + +-func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { +- credentialsKey := driverName + ":" + credType ++func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { ++ credentialsKey := tokenSpec.buildKey() + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ +- WinCredPrefix: strings.ToUpper(sc.cfg.Host), +- ServiceName: strings.ToUpper(sc.cfg.User), ++ WinCredPrefix: strings.ToUpper(tokenSpec.host), ++ ServiceName: strings.ToUpper(tokenSpec.user), + }) +- err := ring.Remove(credentialsKey) ++ err := ring.Remove(string(credentialsKey)) + if err != nil { + logger.Debugf("Failed to delete credentialsKey in Windows Credential Manager. Error: %v", err) + } + } else if runtime.GOOS == "darwin" { +- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ + ServiceName: credentialsKey, + }) +- account := strings.ToUpper(sc.cfg.User) ++ account := strings.ToUpper(tokenSpec.user) + err := ring.Remove(account) + if err != nil { + logger.Debugf("Failed to delete credentialsKey in keychain. Error: %v", err) +@@ -324,25 +341,26 @@ func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, cred + } + } + +-func buildCredentialsKey(host, user, credType string) string { ++func buildCredentialsKey(host, user string, credType tokenType) string { + host = strings.ToUpper(host) + user = strings.ToUpper(user) +- credType = strings.ToUpper(credType) +- return host + ":" + user + ":" + driverName + ":" + credType ++ credTypeStr := strings.ToUpper(string(credType)) ++ return host + ":" + user + ":" + driverName + ":" + credTypeStr + } + + type noopSecureStorageManager struct { + } + +-func newNoopSecureStorageManager() secureStorageManager { ++func newNoopSecureStorageManager() *noopSecureStorageManager { + return &noopSecureStorageManager{} + } + +-func (ssm *noopSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { ++func (ssm *noopSecureStorageManager) setCredential(_ *secureTokenSpec, _ string) { + } + +-func (ssm *noopSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { ++func (ssm *noopSecureStorageManager) getCredential(_ *secureTokenSpec) string { ++ return "" + } + +-func (ssm *noopSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { //TODO implement me ++func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { //TODO implement me + } +-- +2.34.1 + + +From 77a81aa504d9729edc50fab0061ef36dda7dfc82 Mon Sep 17 00:00:00 2001 +From: Kevin Lockwood +Date: Wed, 25 Jun 2025 16:40:59 -0700 +Subject: [PATCH 3/5] SNOW-1825790 Implement safer file based token cache + (#1327) + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/61b822d82822f9672b6749345bf64a044a0532bf.patch +--- + .../gosnowflake/os_specific_posix.go | 25 + + .../gosnowflake/os_specific_windows.go | 12 + + .../gosnowflake/secure_storage_manager.go | 463 +++++++++++++----- + 3 files changed, 370 insertions(+), 130 deletions(-) + create mode 100644 vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go + create mode 100644 vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go b/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go +new file mode 100644 +index 00000000..2d01f5c0 +--- /dev/null ++++ b/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go +@@ -0,0 +1,25 @@ ++//go:build darwin || linux ++ ++package gosnowflake ++ ++import ( ++ "fmt" ++ "os" ++ "syscall" ++) ++ ++func provideFileOwner(file *os.File) (uint32, error) { ++ info, err := file.Stat() ++ if err != nil { ++ return 0, err ++ } ++ return provideOwnerFromStat(info, file.Name()) ++} ++ ++func provideOwnerFromStat(info os.FileInfo, filepath string) (uint32, error) { ++ nativeStat, ok := info.Sys().(*syscall.Stat_t) ++ if !ok { ++ return 0, fmt.Errorf("cannot cast file info for %v to *syscall.Stat_t", filepath) ++ } ++ return nativeStat.Uid, nil ++} +diff --git a/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go b/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go +new file mode 100644 +index 00000000..293123e0 +--- /dev/null ++++ b/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go +@@ -0,0 +1,12 @@ ++// go:build windows ++ ++package gosnowflake ++ ++import ( ++ "errors" ++ "os" ++) ++ ++func provideFileOwner(file *os.File) (uint32, error) { ++ return 0, errors.New("provideFileOwner is unsupported on windows") ++} +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index 6be71a57..ab33874e 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -3,16 +3,21 @@ + package gosnowflake + + import ( ++ "crypto/sha256" ++ "encoding/hex" + "encoding/json" ++ "errors" + "fmt" ++ "github.com/99designs/keyring" ++ "io" + "os" ++ "os/user" + "path/filepath" + "runtime" ++ "strconv" + "strings" + "sync" + "time" +- +- "github.com/99designs/keyring" + ) + + type tokenType string +@@ -23,17 +28,27 @@ const ( + ) + + const ( +- driverName = "SNOWFLAKE-GO-DRIVER" + credCacheDirEnv = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR" +- credCacheFileName = "temporary_credential.json" ++ credCacheFileName = "credential_cache_v1.json" + ) + ++type cacheDirConf struct { ++ envVar string ++ pathSegments []string ++} ++ ++var defaultLinuxCacheDirConf = []cacheDirConf{ ++ {envVar: credCacheDirEnv, pathSegments: []string{}}, ++ {envVar: "XDG_CACHE_DIR", pathSegments: []string{"snowflake"}}, ++ {envVar: "HOME", pathSegments: []string{".cache", "snowflake"}}, ++} ++ + type secureTokenSpec struct { + host, user string + tokenType tokenType + } + +-func (t *secureTokenSpec) buildKey() string { ++func (t *secureTokenSpec) buildKey() (string, error) { + return buildCredentialsKey(t.host, t.user, t.tokenType) + } + +@@ -69,181 +84,328 @@ func newSecureStorageManager() secureStorageManager { + logger.Debugf("failed to create credentials cache dir. %v", err) + return newNoopSecureStorageManager() + } +- return ssm ++ return &threadSafeSecureStorageManager{&sync.Mutex{}, ssm} + case "darwin", "windows": +- return newKeyringBasedSecureStorageManager() ++ return &threadSafeSecureStorageManager{&sync.Mutex{}, newKeyringBasedSecureStorageManager()} + default: ++ logger.Warnf("OS %v does not support credentials cache", runtime.GOOS) + return newNoopSecureStorageManager() + } + } + + type fileBasedSecureStorageManager struct { +- credCacheFilePath string +- localCredCache map[string]string +- credCacheLock sync.RWMutex ++ credDirPath string + } + + func newFileBasedSecureStorageManager() (*fileBasedSecureStorageManager, error) { +- ssm := &fileBasedSecureStorageManager{ +- localCredCache: map[string]string{}, +- credCacheLock: sync.RWMutex{}, +- } +- credCacheDir := ssm.buildCredCacheDirPath() +- if err := ssm.createCacheDir(credCacheDir); err != nil { ++ credDirPath, err := buildCredCacheDirPath(defaultLinuxCacheDirConf) ++ if err != nil { + return nil, err + } +- credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) +- logger.Infof("Credentials cache path: %v", credCacheFilePath) +- ssm.credCacheFilePath = credCacheFilePath ++ ssm := &fileBasedSecureStorageManager{ ++ credDirPath: credDirPath, ++ } + return ssm, nil + } + +-func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { +- _, err := os.Stat(credCacheDir) +- if os.IsNotExist(err) { +- if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { +- return fmt.Errorf("failed to create cache directory. %v, err: %v", credCacheDir, err) ++func lookupCacheDir(envVar string, pathSegments ...string) (string, error) { ++ envVal := os.Getenv(envVar) ++ if envVal == "" { ++ return "", fmt.Errorf("environment variable %s not set", envVar) ++ } ++ ++ fileInfo, err := os.Stat(envVal) ++ if err != nil { ++ return "", fmt.Errorf("failed to stat %s=%s, due to %v", envVar, envVal, err) ++ } ++ ++ if !fileInfo.IsDir() { ++ return "", fmt.Errorf("environment variable %s=%s is not a directory", envVar, envVal) ++ } ++ ++ cacheDir := filepath.Join(envVal, filepath.Join(pathSegments...)) ++ parentOfCacheDir := cacheDir[:strings.LastIndex(cacheDir, "/")] ++ ++ if err = os.MkdirAll(parentOfCacheDir, os.FileMode(0755)); err != nil { ++ return "", err ++ } ++ ++ // We don't check if permissions are incorrect here if a directory exists, because we check it later. ++ if err = os.Mkdir(cacheDir, os.FileMode(0700)); err != nil && !errors.Is(err, os.ErrExist) { ++ return "", err ++ } ++ ++ return cacheDir, nil ++} ++ ++func buildCredCacheDirPath(confs []cacheDirConf) (string, error) { ++ for _, conf := range confs { ++ path, err := lookupCacheDir(conf.envVar, conf.pathSegments...) ++ if err != nil { ++ logger.Debugf("Skipping %s in cache directory lookup due to %v", conf.envVar, err) ++ } else { ++ logger.Debugf("Using %s as cache directory", path) ++ return path, nil + } +- return nil + } +- return err ++ ++ return "", errors.New("no credentials cache directory found") + } + +-func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { +- credCacheDir := os.Getenv(credCacheDirEnv) +- if credCacheDir != "" { +- return credCacheDir ++func (ssm *fileBasedSecureStorageManager) getTokens(data map[string]any) map[string]interface{} { ++ val, ok := data["tokens"] ++ if !ok { ++ return map[string]interface{}{} + } +- home := os.Getenv("HOME") +- if home == "" { +- logger.Info("HOME is blank") +- return "" ++ ++ tokens, ok := val.(map[string]interface{}) ++ if !ok { ++ return map[string]interface{}{} + } +- credCacheDir = filepath.Join(home, ".cache", "snowflake") +- return credCacheDir ++ ++ return tokens ++} ++ ++func (ssm *fileBasedSecureStorageManager) withLock(action func(cacheFile *os.File)) { ++ err := ssm.lockFile() ++ if err != nil { ++ logger.Warnf("Unable to lock cache. %v", err) ++ return ++ } ++ defer ssm.unlockFile() ++ ++ ssm.withCacheFile(action) ++} ++ ++func (ssm *fileBasedSecureStorageManager) withCacheFile(action func(*os.File)) { ++ cacheFile, err := os.OpenFile(ssm.credFilePath(), os.O_CREATE|os.O_RDWR, 0600) ++ if err != nil { ++ logger.Warnf("cannot access %v. %v", ssm.credFilePath(), err) ++ return ++ } ++ defer func(file *os.File) { ++ if err := file.Close(); err != nil { ++ logger.Warnf("cannot release file descriptor for %v. %v", ssm.credFilePath(), err) ++ } ++ }(cacheFile) ++ ++ cacheDir, err := os.Open(ssm.credDirPath) ++ if err != nil { ++ logger.Warnf("cannot access %v. %v", ssm.credDirPath, err) ++ } ++ defer func(file *os.File) { ++ if err := file.Close(); err != nil { ++ logger.Warnf("cannot release file descriptor for %v. %v", cacheDir, err) ++ } ++ }(cacheDir) ++ ++ if err := ssm.ensurePermissionsAndOwner(cacheFile, 0600); err != nil { ++ logger.Warnf("failed to ensure permission for temporary cache file. %v", err) ++ return ++ } ++ if err := ssm.ensurePermissionsAndOwner(cacheDir, 0700|os.ModeDir); err != nil { ++ logger.Warnf("failed to ensure permission for temporary cache dir. %v", err) ++ return ++ } ++ ++ action(cacheFile) + } + + func (ssm *fileBasedSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { +- if value == "" { +- logger.Debug("no token provided") +- } else { +- credentialsKey := tokenSpec.buildKey() +- ssm.credCacheLock.Lock() +- defer ssm.credCacheLock.Unlock() +- ssm.localCredCache[credentialsKey] = value ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return ++ } + +- j, err := json.Marshal(ssm.localCredCache) ++ ssm.withLock(func(cacheFile *os.File) { ++ credCache, err := ssm.readTemporaryCacheFile(cacheFile) + if err != nil { +- logger.Warnf("failed to convert credential to JSON.") ++ logger.Warnf("Error while reading cache file. %v", err) + return + } ++ tokens := ssm.getTokens(credCache) ++ tokens[credentialsKey] = value ++ credCache["tokens"] = tokens ++ err = ssm.writeTemporaryCacheFile(credCache, cacheFile) ++ if err != nil { ++ logger.Warnf("Set credential failed. Unable to write cache. %v", err) ++ } ++ }) ++} + +- logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) +- credCacheLockFileName := ssm.credCacheFilePath + ".lck" +- logger.Debugf("Creating lock file. %v", credCacheLockFileName) +- err = os.Mkdir(credCacheLockFileName, 0600) +- +- switch { +- case os.IsExist(err): +- statinfo, err := os.Stat(credCacheLockFileName) +- if err != nil { +- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) +- return +- } +- if time.Since(statinfo.ModTime()) < 15*time.Minute { +- logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) +- return +- } +- if err = os.Remove(credCacheLockFileName); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return +- } +- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return +- } ++func (ssm *fileBasedSecureStorageManager) lockPath() string { ++ return filepath.Join(ssm.credDirPath, credCacheFileName+".lck") ++} ++ ++func (ssm *fileBasedSecureStorageManager) lockFile() error { ++ const numRetries = 10 ++ const retryInterval = 100 * time.Millisecond ++ lockPath := ssm.lockPath() ++ ++ fileInfo, err := os.Stat(lockPath) ++ if err != nil && !errors.Is(err, os.ErrNotExist) { ++ return fmt.Errorf("failed to stat %v and determine if lock is stale. err: %v", lockPath, err) ++ } ++ ++ // removing stale lock ++ now := time.Now() ++ if !errors.Is(err, os.ErrNotExist) && fileInfo.ModTime().Add(time.Second).UnixNano() < now.UnixNano() { ++ logger.Debugf("removing credentials cache lock file, stale for %vms", (now.UnixNano()-fileInfo.ModTime().UnixNano())/1000/1000) ++ err = os.Remove(lockPath) ++ if err != nil { ++ return fmt.Errorf("failed to remove %v while trying to remove stale lock. err: %v", lockPath, err) + } +- defer os.RemoveAll(credCacheLockFileName) ++ } + +- if err = os.WriteFile(ssm.credCacheFilePath, j, 0644); err != nil { +- logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) ++ locked := false ++ for i := 0; i < numRetries; i++ { ++ err := os.Mkdir(lockPath, 0700) ++ if err != nil { ++ if errors.Is(err, os.ErrExist) { ++ time.Sleep(retryInterval) ++ continue ++ } ++ return fmt.Errorf("failed to create cache lock: %v, err: %v", lockPath, err) + } ++ locked = true ++ break ++ } ++ if !locked { ++ return fmt.Errorf("failed to lock cache. lockPath: %v", lockPath) ++ } ++ return nil ++} ++ ++func (ssm *fileBasedSecureStorageManager) unlockFile() { ++ lockPath := ssm.lockPath() ++ err := os.Remove(lockPath) ++ if err != nil { ++ logger.Warnf("Failed to unlock cache lock: %v. %v", lockPath, err) + } + } + + func (ssm *fileBasedSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { +- credentialsKey := tokenSpec.buildKey() +- ssm.credCacheLock.Lock() +- defer ssm.credCacheLock.Unlock() +- localCredCache := ssm.readTemporaryCacheFile() +- cred := localCredCache[credentialsKey] +- if cred != "" { +- logger.Debug("Successfully read token. Returning as string") +- } else { +- logger.Debug("Returned credential is empty") ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return "" + } +- return cred ++ ++ ret := "" ++ ssm.withLock(func(cacheFile *os.File) { ++ credCache, err := ssm.readTemporaryCacheFile(cacheFile) ++ if err != nil { ++ logger.Warnf("Error while reading cache file. %v", err) ++ return ++ } ++ cred, ok := ssm.getTokens(credCache)[credentialsKey] ++ if !ok { ++ return ++ } ++ ++ credStr, ok := cred.(string) ++ if !ok { ++ return ++ } ++ ++ ret = credStr ++ }) ++ return ret + } + +-func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { +- jsonData, err := os.ReadFile(ssm.credCacheFilePath) ++func (ssm *fileBasedSecureStorageManager) credFilePath() string { ++ return filepath.Join(ssm.credDirPath, credCacheFileName) ++} ++ ++func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, expectedMode os.FileMode) error { ++ fileInfo, err := f.Stat() + if err != nil { +- logger.Debugf("Failed to read credential file: %v", err) +- return nil ++ return err ++ } ++ ++ if fileInfo.Mode().Perm() != expectedMode&os.ModePerm { ++ return fmt.Errorf("incorrect permissions(%v, expected %v) for credential file", fileInfo.Mode(), expectedMode) ++ } ++ ++ ownerUID, err := provideFileOwner(f) ++ if err != nil && !errors.Is(err, os.ErrNotExist) { ++ return err + } +- err = json.Unmarshal([]byte(jsonData), &ssm.localCredCache) ++ currentUser, err := user.Current() + if err != nil { +- logger.Debugf("failed to read JSON. Err: %v", err) ++ return err ++ } ++ if errors.Is(err, os.ErrNotExist) { + return nil + } ++ if strconv.Itoa(int(ownerUID)) != currentUser.Uid { ++ return errors.New("incorrect owner of " + ssm.credDirPath) ++ } ++ return nil ++} ++ ++func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile(cacheFile *os.File) (map[string]any, error) { ++ ++ jsonData, err := io.ReadAll(cacheFile) ++ if err != nil { ++ logger.Warnf("Failed to read credential cache file. %v.\n", err) ++ return map[string]any{}, nil ++ } ++ if _, err = cacheFile.Seek(0, 0); err != nil { ++ return map[string]any{}, fmt.Errorf("cannot seek to the beginning of a cache file. %v", err) ++ } ++ ++ if len(jsonData) == 0 { ++ // Happens when the file didn't exist before. ++ return map[string]any{}, nil ++ } + +- return ssm.localCredCache ++ credentialsMap := map[string]any{} ++ err = json.Unmarshal(jsonData, &credentialsMap) ++ if err != nil { ++ return map[string]any{}, fmt.Errorf("failed to unmarshal credential cache file. %v", err) ++ } ++ ++ return credentialsMap, nil + } + + func (ssm *fileBasedSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { +- ssm.credCacheLock.Lock() +- defer ssm.credCacheLock.Unlock() +- credentialsKey := tokenSpec.buildKey() +- delete(ssm.localCredCache, credentialsKey) +- j, err := json.Marshal(ssm.localCredCache) ++ credentialsKey, err := tokenSpec.buildKey() + if err != nil { +- logger.Warnf("failed to convert credential to JSON.") ++ logger.Warn(err) + return + } +- ssm.writeTemporaryCacheFile(j) +-} + +-func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) { +- logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) +- credCacheLockFileName := ssm.credCacheFilePath + ".lck" +- err := os.Mkdir(credCacheLockFileName, 0600) +- logger.Debugf("Creating lock file. %v", credCacheLockFileName) +- +- switch { +- case os.IsExist(err): +- statinfo, err := os.Stat(credCacheLockFileName) ++ ssm.withLock(func(cacheFile *os.File) { ++ credCache, err := ssm.readTemporaryCacheFile(cacheFile) + if err != nil { +- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) +- return +- } +- if time.Since(statinfo.ModTime()) < 15*time.Minute { +- logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) +- return +- } +- if err = os.Remove(credCacheLockFileName); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ logger.Warnf("Error while reading cache file. %v", err) + return + } +- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return ++ delete(ssm.getTokens(credCache), credentialsKey) ++ ++ err = ssm.writeTemporaryCacheFile(credCache, cacheFile) ++ if err != nil { ++ logger.Warnf("Set credential failed. Unable to write cache. %v", err) + } ++ }) ++} ++ ++func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(cache map[string]any, cacheFile *os.File) error { ++ bytes, err := json.Marshal(cache) ++ if err != nil { ++ return fmt.Errorf("failed to marshal credential cache map. %w", err) + } +- defer os.RemoveAll(credCacheLockFileName) + +- if err = os.WriteFile(ssm.credCacheFilePath, input, 0644); err != nil { +- logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) ++ if err = cacheFile.Truncate(0); err != nil { ++ return fmt.Errorf("error while truncating credentials cache. %v", err) ++ } ++ _, err = cacheFile.Write(bytes) ++ if err != nil { ++ return fmt.Errorf("failed to write the credential cache file: %w", err) + } ++ return nil + } + + type keyringSecureStorageManager struct { +@@ -257,7 +419,11 @@ func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec + if value == "" { + logger.Debug("no token provided") + } else { +- credentialsKey := tokenSpec.buildKey() ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return ++ } + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(tokenSpec.host), +@@ -288,7 +454,11 @@ func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec + + func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { + cred := "" +- credentialsKey := tokenSpec.buildKey() ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return "" ++ } + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(tokenSpec.host), +@@ -319,7 +489,11 @@ func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec + } + + func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { +- credentialsKey := tokenSpec.buildKey() ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return ++ } + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(tokenSpec.host), +@@ -341,11 +515,17 @@ func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenS + } + } + +-func buildCredentialsKey(host, user string, credType tokenType) string { +- host = strings.ToUpper(host) +- user = strings.ToUpper(user) +- credTypeStr := strings.ToUpper(string(credType)) +- return host + ":" + user + ":" + driverName + ":" + credTypeStr ++func buildCredentialsKey(host, user string, credType tokenType) (string, error) { ++ if host == "" { ++ return "", errors.New("host is not provided to store in token cache, skipping") ++ } ++ if user == "" { ++ return "", errors.New("user is not provided to store in token cache, skipping") ++ } ++ plainCredKey := host + ":" + user + ":" + string(credType) ++ checksum := sha256.New() ++ checksum.Write([]byte(plainCredKey)) ++ return hex.EncodeToString(checksum.Sum(nil)), nil + } + + type noopSecureStorageManager struct { +@@ -362,5 +542,28 @@ func (ssm *noopSecureStorageManager) getCredential(_ *secureTokenSpec) string { + return "" + } + +-func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { //TODO implement me ++func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { ++} ++ ++type threadSafeSecureStorageManager struct { ++ mu *sync.Mutex ++ delegate secureStorageManager ++} ++ ++func (ssm *threadSafeSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { ++ ssm.mu.Lock() ++ defer ssm.mu.Unlock() ++ ssm.delegate.setCredential(tokenSpec, value) ++} ++ ++func (ssm *threadSafeSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { ++ ssm.mu.Lock() ++ defer ssm.mu.Unlock() ++ return ssm.delegate.getCredential(tokenSpec) ++} ++ ++func (ssm *threadSafeSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { ++ ssm.mu.Lock() ++ defer ssm.mu.Unlock() ++ ssm.delegate.deleteCredential(tokenSpec) + } +-- +2.34.1 + + +From e8e6d49a988ac24c0691434b3961911f091e0a90 Mon Sep 17 00:00:00 2001 +From: Piotr Fus +Date: Tue, 1 Apr 2025 10:55:30 +0200 +Subject: [PATCH 4/5] =?UTF-8?q?SNOW-1825790=20Fix=20error=20message=20when?= + =?UTF-8?q?=20credentials=20file=20owner=20does=20not=20m=E2=80=A6=20(#134?= + =?UTF-8?q?9)?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/40e4f5c874dcc003f6318a355b66da8056db5ce4.patch +--- + .../snowflakedb/gosnowflake/secure_storage_manager.go | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index ab33874e..a823a52e 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -340,7 +340,7 @@ func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, + return nil + } + if strconv.Itoa(int(ownerUID)) != currentUser.Uid { +- return errors.New("incorrect owner of " + ssm.credDirPath) ++ return errors.New("incorrect owner of " + f.Name()) + } + return nil + } +-- +2.34.1 + + +From 45c6ddc1d8336398a0d3d5b69da5e21f5f4a8574 Mon Sep 17 00:00:00 2001 +From: Kevin Lockwood +Date: Wed, 25 Jun 2025 16:46:39 -0700 +Subject: [PATCH 5/5] [Low] Fix CVE-2025-46327 + +All other patches in this file are supporting patches backported so that +this patch can apply. + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/ba94a4800e23621eff558ef18ce4b96ec5489ff0.patch +--- + .../gosnowflake/client_configuration.go | 25 ++---------- + .../gosnowflake/os_specific_posix.go | 38 +++++++++++++++++++ + .../gosnowflake/os_specific_windows.go | 8 ++++ + .../gosnowflake/secure_storage_manager.go | 37 +++++++++++------- + 4 files changed, 73 insertions(+), 35 deletions(-) + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/client_configuration.go b/vendor/github.com/snowflakedb/gosnowflake/client_configuration.go +index 0f6526d4..a28dc0f7 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/client_configuration.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/client_configuration.go +@@ -9,7 +9,6 @@ import ( + "os" + "path" + "path/filepath" +- "runtime" + "strings" + ) + +@@ -117,11 +116,9 @@ func parseClientConfiguration(filePath string) (*ClientConfig, error) { + if filePath == "" { + return nil, nil + } +- fileContents, err := os.ReadFile(filePath) +- if err != nil { +- return nil, parsingClientConfigError(err) +- } +- err = validateCfgPerm(filePath) ++ // Check if group (5th LSB) or others (2nd LSB) have a write permission to the file ++ expectedPerm := os.FileMode(1<<4 | 1<<1) ++ fileContents, err := getFileContents(filePath, expectedPerm) + if err != nil { + return nil, parsingClientConfigError(err) + } +@@ -187,22 +184,6 @@ func validateLogLevel(clientConfig ClientConfig) error { + return nil + } + +-func validateCfgPerm(filePath string) error { +- if runtime.GOOS == "windows" { +- return nil +- } +- stat, err := os.Stat(filePath) +- if err != nil { +- return err +- } +- perm := stat.Mode() +- // Check if group (5th LSB) or others (2nd LSB) have a write permission to the file +- if perm&(1<<4) != 0 || perm&(1<<1) != 0 { +- return fmt.Errorf("configuration file: %s can be modified by group or others", filePath) +- } +- return nil +-} +- + func toLogLevel(logLevelString string) (string, error) { + var logLevel = strings.ToUpper(logLevelString) + switch logLevel { +diff --git a/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go b/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go +index 2d01f5c0..1a041b7a 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go +@@ -4,6 +4,7 @@ package gosnowflake + + import ( + "fmt" ++ "io" + "os" + "syscall" + ) +@@ -23,3 +24,40 @@ func provideOwnerFromStat(info os.FileInfo, filepath string) (uint32, error) { + } + return nativeStat.Uid, nil + } ++ ++func getFileContents(filePath string, expectedPerm os.FileMode) ([]byte, error) { ++ // open the file with read only and no symlink flags ++ file, err := os.OpenFile(filePath, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) ++ if err != nil { ++ return nil, err ++ } ++ defer file.Close() ++ ++ // validate file permissions and owner ++ if err = validateFilePermissionBits(file, expectedPerm); err != nil { ++ return nil, err ++ } ++ if err = ensureFileOwner(file); err != nil { ++ return nil, err ++ } ++ ++ // read the file ++ fileContents, err := io.ReadAll(file) ++ if err != nil { ++ return nil, err ++ } ++ ++ return fileContents, nil ++} ++ ++func validateFilePermissionBits(f *os.File, expectedPerm os.FileMode) error { ++ fileInfo, err := f.Stat() ++ if err != nil { ++ return err ++ } ++ filePerm := fileInfo.Mode() ++ if filePerm&expectedPerm != 0 { ++ return fmt.Errorf("incorrect permissions of %s", f.Name()) ++ } ++ return nil ++} +diff --git a/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go b/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go +index 293123e0..7303ed1a 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go +@@ -10,3 +10,11 @@ import ( + func provideFileOwner(file *os.File) (uint32, error) { + return 0, errors.New("provideFileOwner is unsupported on windows") + } ++ ++func getFileContents(filePath string, expectedPerm os.FileMode) ([]byte, error) { ++ fileContents, err := os.ReadFile(filePath) ++ if err != nil { ++ return nil, err ++ } ++ return fileContents, nil ++} +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index a823a52e..7c173e5a 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -8,7 +8,6 @@ import ( + "encoding/json" + "errors" + "fmt" +- "github.com/99designs/keyring" + "io" + "os" + "os/user" +@@ -18,6 +17,8 @@ import ( + "strings" + "sync" + "time" ++ ++ "github.com/99designs/keyring" + ) + + type tokenType string +@@ -199,11 +200,19 @@ func (ssm *fileBasedSecureStorageManager) withCacheFile(action func(*os.File)) { + } + }(cacheDir) + +- if err := ssm.ensurePermissionsAndOwner(cacheFile, 0600); err != nil { ++ if err := ensureFileOwner(cacheFile); err != nil { ++ logger.Warnf("failed to ensure owner for temporary cache file. %v", err) ++ return ++ } ++ if err := ensureFilePermissions(cacheFile, 0600); err != nil { + logger.Warnf("failed to ensure permission for temporary cache file. %v", err) + return + } +- if err := ssm.ensurePermissionsAndOwner(cacheDir, 0700|os.ModeDir); err != nil { ++ if err := ensureFileOwner(cacheDir); err != nil { ++ logger.Warnf("failed to ensure owner for temporary cache dir. %v", err) ++ return ++ } ++ if err := ensureFilePermissions(cacheDir, 0700|os.ModeDir); err != nil { + logger.Warnf("failed to ensure permission for temporary cache dir. %v", err) + return + } +@@ -318,16 +327,7 @@ func (ssm *fileBasedSecureStorageManager) credFilePath() string { + return filepath.Join(ssm.credDirPath, credCacheFileName) + } + +-func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, expectedMode os.FileMode) error { +- fileInfo, err := f.Stat() +- if err != nil { +- return err +- } +- +- if fileInfo.Mode().Perm() != expectedMode&os.ModePerm { +- return fmt.Errorf("incorrect permissions(%v, expected %v) for credential file", fileInfo.Mode(), expectedMode) +- } +- ++func ensureFileOwner(f *os.File) error { + ownerUID, err := provideFileOwner(f) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return err +@@ -345,6 +345,17 @@ func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, + return nil + } + ++func ensureFilePermissions(f *os.File, expectedMode os.FileMode) error { ++ fileInfo, err := f.Stat() ++ if err != nil { ++ return err ++ } ++ if fileInfo.Mode().Perm() != expectedMode&os.ModePerm { ++ return fmt.Errorf("incorrect permissions(%v, expected %v) for credential file", fileInfo.Mode(), expectedMode) ++ } ++ return nil ++} ++ + func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile(cacheFile *os.File) (map[string]any, error) { + + jsonData, err := io.ReadAll(cacheFile) +-- +2.34.1 + diff --git a/SPECS/telegraf/telegraf.spec b/SPECS/telegraf/telegraf.spec index a6ccc303308..f22cd3d5502 100644 --- a/SPECS/telegraf/telegraf.spec +++ b/SPECS/telegraf/telegraf.spec @@ -24,6 +24,7 @@ Patch9: CVE-2025-27144.patch Patch10: CVE-2025-30215.patch Patch11: CVE-2025-22872.patch Patch12: CVE-2025-29923.patch +Patch13: CVE-2025-46327.patch BuildRequires: golang BuildRequires: systemd-devel @@ -90,6 +91,7 @@ fi %changelog * Wed Jun 18 2025 Kevin Lockwood - 1.31.0-11 - Patch CVE-2025-29923 +- Patch CVE-2025-46327 * Tue Apr 22 2025 Mayank Singh - 1.31.0-10 - Fix CVE-2025-22872 with an upstream patch @@ -103,7 +105,7 @@ fi * Mon Mar 31 2025 Kanishk Bansal - 1.31.0-7 - Patch CVE-2025-30204 -* Tue Mar 26 2025 Sreeniavsulu Malavathula - 1.31.0-6 +* Wed Mar 26 2025 Sreeniavsulu Malavathula - 1.31.0-6 - Fix CVE-2025-22870, CVE-2024-51744 with an upstream patch * Wed Mar 05 2025 Kanishk Bansal - 1.31.0-5 From d272663819f994c4feea340c8b4c076d2b089fea Mon Sep 17 00:00:00 2001 From: Kevin Lockwood Date: Wed, 2 Jul 2025 15:45:23 -0700 Subject: [PATCH 4/4] Split CVE patch from backported prerequisite patches --- SPECS/telegraf/CVE-2025-46327-prereqs.patch | 1636 ++++++++++++++++++ SPECS/telegraf/CVE-2025-46327.patch | 1639 +------------------ SPECS/telegraf/telegraf.spec | 3 +- 3 files changed, 1639 insertions(+), 1639 deletions(-) create mode 100644 SPECS/telegraf/CVE-2025-46327-prereqs.patch diff --git a/SPECS/telegraf/CVE-2025-46327-prereqs.patch b/SPECS/telegraf/CVE-2025-46327-prereqs.patch new file mode 100644 index 00000000000..e0ae316d203 --- /dev/null +++ b/SPECS/telegraf/CVE-2025-46327-prereqs.patch @@ -0,0 +1,1636 @@ +From 2f9da773e6e0ea2e7653b0a379304c09e4422a0a Mon Sep 17 00:00:00 2001 +From: Kevin Lockwood +Date: Wed, 25 Jun 2025 16:38:08 -0700 +Subject: [PATCH 1/4] SNOW-1825790 Separate secure storage managers based on + file and keyring (#1295) + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/e926883b12016eb30ae25c2027f2cf70714eb2c5.patch +--- + .../snowflakedb/gosnowflake/auth.go | 12 +- + .../gosnowflake/secure_storage_manager.go | 384 ++++++++++-------- + 2 files changed, 230 insertions(+), 166 deletions(-) + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/auth.go b/vendor/github.com/snowflakedb/gosnowflake/auth.go +index 6d069a91..888cd1ba 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/auth.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/auth.go +@@ -364,10 +364,10 @@ func authenticate( + logger.WithContext(ctx).Errorln("Authentication FAILED") + sc.rest.TokenAccessor.SetTokens("", "", -1) + if sessionParameters[clientRequestMfaToken] == true { +- deleteCredential(sc, mfaToken) ++ credentialsStorage.deleteCredential(sc, mfaToken) + } + if sessionParameters[clientStoreTemporaryCredential] == true { +- deleteCredential(sc, idToken) ++ credentialsStorage.deleteCredential(sc, idToken) + } + code, err := strconv.Atoi(respd.Code) + if err != nil { +@@ -384,11 +384,11 @@ func authenticate( + sc.rest.TokenAccessor.SetTokens(respd.Data.Token, respd.Data.MasterToken, respd.Data.SessionID) + if sessionParameters[clientRequestMfaToken] == true { + token := respd.Data.MfaToken +- setCredential(sc, mfaToken, token) ++ credentialsStorage.setCredential(sc, mfaToken, token) + } + if sessionParameters[clientStoreTemporaryCredential] == true { + token := respd.Data.IDToken +- setCredential(sc, idToken, token) ++ credentialsStorage.setCredential(sc, idToken, token) + } + return &respd.Data, nil + } +@@ -565,9 +565,9 @@ func authenticateWithConfig(sc *snowflakeConn) error { + } + + func fillCachedIDToken(sc *snowflakeConn) { +- getCredential(sc, idToken) ++ credentialsStorage.getCredential(sc, idToken) + } + + func fillCachedMfaToken(sc *snowflakeConn) { +- getCredential(sc, mfaToken) ++ credentialsStorage.getCredential(sc, mfaToken) + } +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index da8610e3..fc392662 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -4,6 +4,7 @@ package gosnowflake + + import ( + "encoding/json" ++ "fmt" + "os" + "path/filepath" + "runtime" +@@ -20,68 +21,231 @@ const ( + credCacheFileName = "temporary_credential.json" + ) + +-var ( +- credCacheDir = "" +- credCache = "" +- localCredCache = map[string]string{} +-) ++type secureStorageManager interface { ++ setCredential(sc *snowflakeConn, credType, token string) ++ getCredential(sc *snowflakeConn, credType string) ++ deleteCredential(sc *snowflakeConn, credType string) ++} + +-var ( +- credCacheLock sync.RWMutex +-) ++var credentialsStorage = newSecureStorageManager() ++ ++func newSecureStorageManager() secureStorageManager { ++ switch runtime.GOOS { ++ case "linux": ++ return newFileBasedSecureStorageManager() ++ case "darwin", "windows": ++ return newKeyringBasedSecureStorageManager() ++ default: ++ return newNoopSecureStorageManager() ++ } ++} ++ ++type fileBasedSecureStorageManager struct { ++ credCacheFilePath string ++ localCredCache map[string]string ++ credCacheLock sync.RWMutex ++} ++ ++func newFileBasedSecureStorageManager() secureStorageManager { ++ ssm := &fileBasedSecureStorageManager{ ++ localCredCache: map[string]string{}, ++ credCacheLock: sync.RWMutex{}, ++ } ++ credCacheDir := ssm.buildCredCacheDirPath() ++ if err := ssm.createCacheDir(credCacheDir); err != nil { ++ logger.Debugf("failed to create credentials cache dir. %v", err) ++ return newNoopSecureStorageManager() ++ } ++ credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) ++ logger.Infof("Credentials cache path: %v", credCacheFilePath) ++ ssm.credCacheFilePath = credCacheFilePath ++ return ssm ++} ++ ++func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { ++ _, err := os.Stat(credCacheDir) ++ if os.IsNotExist(err) { ++ if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { ++ return fmt.Errorf("failed to create cache directory. %v, err: %v", credCacheDir, err) ++ } ++ return nil ++ } ++ return err ++} ++ ++func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { ++ credCacheDir := os.Getenv(credCacheDirEnv) ++ if credCacheDir != "" { ++ return credCacheDir ++ } ++ home := os.Getenv("HOME") ++ if home == "" { ++ logger.Info("HOME is blank") ++ return "" ++ } ++ credCacheDir = filepath.Join(home, ".cache", "snowflake") ++ return credCacheDir ++} ++ ++func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { ++ if token == "" { ++ logger.Debug("no token provided") ++ } else { ++ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ ssm.credCacheLock.Lock() ++ defer ssm.credCacheLock.Unlock() ++ ssm.localCredCache[credentialsKey] = token ++ ++ j, err := json.Marshal(ssm.localCredCache) ++ if err != nil { ++ logger.Warnf("failed to convert credential to JSON.") ++ return ++ } ++ ++ logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) ++ credCacheLockFileName := ssm.credCacheFilePath + ".lck" ++ logger.Debugf("Creating lock file. %v", credCacheLockFileName) ++ err = os.Mkdir(credCacheLockFileName, 0600) + +-func createCredentialCacheDir() { +- credCacheDir = os.Getenv(credCacheDirEnv) +- if credCacheDir == "" { +- switch runtime.GOOS { +- case "windows": +- credCacheDir = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local", "Snowflake", "Caches") +- case "darwin": +- home := os.Getenv("HOME") +- if home == "" { +- logger.Info("HOME is blank.") ++ switch { ++ case os.IsExist(err): ++ statinfo, err := os.Stat(credCacheLockFileName) ++ if err != nil { ++ logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) ++ return ++ } ++ if time.Since(statinfo.ModTime()) < 15*time.Minute { ++ logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) ++ return ++ } ++ if err = os.Remove(credCacheLockFileName); err != nil { ++ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ return + } +- credCacheDir = filepath.Join(home, "Library", "Caches", "Snowflake") +- default: +- home := os.Getenv("HOME") +- if home == "" { +- logger.Info("HOME is blank") ++ if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { ++ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ return + } +- credCacheDir = filepath.Join(home, ".cache", "snowflake") ++ } ++ defer os.RemoveAll(credCacheLockFileName) ++ ++ if err = os.WriteFile(ssm.credCacheFilePath, j, 0644); err != nil { ++ logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) + } + } ++} + +- if _, err := os.Stat(credCacheDir); os.IsNotExist(err) { +- if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { +- logger.Debugf("Failed to create cache directory. %v, err: %v. ignored\n", credCacheDir, err) ++func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { ++ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ ssm.credCacheLock.Lock() ++ defer ssm.credCacheLock.Unlock() ++ localCredCache := ssm.readTemporaryCacheFile() ++ cred := localCredCache[credentialsKey] ++ if cred != "" { ++ logger.Debug("Successfully read token. Returning as string") ++ } else { ++ logger.Debug("Returned credential is empty") ++ } ++ ++ if credType == idToken { ++ sc.cfg.IDToken = cred ++ } else if credType == mfaToken { ++ sc.cfg.MfaToken = cred ++ } else { ++ logger.Debugf("Unrecognized type %v for local cached credential", credType) ++ } ++} ++ ++func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { ++ jsonData, err := os.ReadFile(ssm.credCacheFilePath) ++ if err != nil { ++ logger.Debugf("Failed to read credential file: %v", err) ++ return nil ++ } ++ err = json.Unmarshal([]byte(jsonData), &ssm.localCredCache) ++ if err != nil { ++ logger.Debugf("failed to read JSON. Err: %v", err) ++ return nil ++ } ++ ++ return ssm.localCredCache ++} ++ ++func (ssm *fileBasedSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { ++ ssm.credCacheLock.Lock() ++ defer ssm.credCacheLock.Unlock() ++ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ delete(ssm.localCredCache, credentialsKey) ++ j, err := json.Marshal(ssm.localCredCache) ++ if err != nil { ++ logger.Warnf("failed to convert credential to JSON.") ++ return ++ } ++ ssm.writeTemporaryCacheFile(j) ++} ++ ++func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) { ++ logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) ++ credCacheLockFileName := ssm.credCacheFilePath + ".lck" ++ err := os.Mkdir(credCacheLockFileName, 0600) ++ logger.Debugf("Creating lock file. %v", credCacheLockFileName) ++ ++ switch { ++ case os.IsExist(err): ++ statinfo, err := os.Stat(credCacheLockFileName) ++ if err != nil { ++ logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) ++ return ++ } ++ if time.Since(statinfo.ModTime()) < 15*time.Minute { ++ logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) ++ return + } ++ if err = os.Remove(credCacheLockFileName); err != nil { ++ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ return ++ } ++ if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { ++ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ return ++ } ++ } ++ defer os.RemoveAll(credCacheLockFileName) ++ ++ if err = os.WriteFile(ssm.credCacheFilePath, input, 0644); err != nil { ++ logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) + } +- credCache = filepath.Join(credCacheDir, credCacheFileName) +- logger.Infof("Cache directory: %v", credCache) + } + +-func setCredential(sc *snowflakeConn, credType, token string) { ++type keyringSecureStorageManager struct { ++} ++ ++func newKeyringBasedSecureStorageManager() secureStorageManager { ++ return &keyringSecureStorageManager{} ++} ++ ++func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { + if token == "" { + logger.Debug("no token provided") + } else { +- var target string ++ var credentialsKey string + if runtime.GOOS == "windows" { +- target = driverName + ":" + credType ++ credentialsKey = driverName + ":" + credType + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(sc.cfg.Host), + ServiceName: strings.ToUpper(sc.cfg.User), + }) + item := keyring.Item{ +- Key: target, ++ Key: credentialsKey, + Data: []byte(token), + } + if err := ring.Set(item); err != nil { + logger.Debugf("Failed to write to Windows credential manager. Err: %v", err) + } + } else if runtime.GOOS == "darwin" { +- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ +- ServiceName: target, ++ ServiceName: credentialsKey, + }) + account := strings.ToUpper(sc.cfg.User) + item := keyring.Item{ +@@ -91,33 +255,28 @@ func setCredential(sc *snowflakeConn, credType, token string) { + if err := ring.Set(item); err != nil { + logger.Debugf("Failed to write to keychain. Err: %v", err) + } +- } else if runtime.GOOS == "linux" { +- createCredentialCacheDir() +- writeTemporaryCredential(sc, credType, token) +- } else { +- logger.Debug("OS not supported for Local Secure Storage") + } + } + } + +-func getCredential(sc *snowflakeConn, credType string) { +- var target string ++func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { ++ var credentialsKey string + cred := "" + if runtime.GOOS == "windows" { +- target = driverName + ":" + credType ++ credentialsKey = driverName + ":" + credType + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(sc.cfg.Host), + ServiceName: strings.ToUpper(sc.cfg.User), + }) +- i, err := ring.Get(target) ++ i, err := ring.Get(credentialsKey) + if err != nil { +- logger.Debugf("Failed to read target or could not find it in Windows Credential Manager. Error: %v", err) ++ logger.Debugf("Failed to read credentialsKey or could not find it in Windows Credential Manager. Error: %v", err) + } + cred = string(i.Data) + } else if runtime.GOOS == "darwin" { +- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ +- ServiceName: target, ++ ServiceName: credentialsKey, + }) + account := strings.ToUpper(sc.cfg.User) + i, err := ring.Get(account) +@@ -130,11 +289,6 @@ func getCredential(sc *snowflakeConn, credType string) { + } else { + logger.Debug("Successfully read token. Returning as string") + } +- } else if runtime.GOOS == "linux" { +- createCredentialCacheDir() +- cred = readTemporaryCredential(sc, credType) +- } else { +- logger.Debug("OS not supported for Local Secure Storage") + } + + if credType == idToken { +@@ -146,139 +300,49 @@ func getCredential(sc *snowflakeConn, credType string) { + } + } + +-func deleteCredential(sc *snowflakeConn, credType string) { +- target := driverName + ":" + credType ++func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { ++ credentialsKey := driverName + ":" + credType + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(sc.cfg.Host), + ServiceName: strings.ToUpper(sc.cfg.User), + }) +- err := ring.Remove(target) ++ err := ring.Remove(credentialsKey) + if err != nil { +- logger.Debugf("Failed to delete target in Windows Credential Manager. Error: %v", err) ++ logger.Debugf("Failed to delete credentialsKey in Windows Credential Manager. Error: %v", err) + } + } else if runtime.GOOS == "darwin" { +- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ +- ServiceName: target, ++ ServiceName: credentialsKey, + }) + account := strings.ToUpper(sc.cfg.User) + err := ring.Remove(account) + if err != nil { +- logger.Debugf("Failed to delete target in keychain. Error: %v", err) ++ logger.Debugf("Failed to delete credentialsKey in keychain. Error: %v", err) + } +- } else if runtime.GOOS == "linux" { +- deleteTemporaryCredential(sc, credType) + } + } + +-// Reads temporary credential file when OS is Linux. +-func readTemporaryCredential(sc *snowflakeConn, credType string) string { +- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) +- credCacheLock.Lock() +- defer credCacheLock.Unlock() +- localCredCache := readTemporaryCacheFile() +- cred := localCredCache[target] +- if cred != "" { +- logger.Debug("Successfully read token. Returning as string") +- } else { +- logger.Debug("Returned credential is empty") +- } +- return cred ++func buildCredentialsKey(host, user, credType string) string { ++ host = strings.ToUpper(host) ++ user = strings.ToUpper(user) ++ credType = strings.ToUpper(credType) ++ return host + ":" + user + ":" + driverName + ":" + credType + } + +-// Writes to temporary credential file when OS is Linux. +-func writeTemporaryCredential(sc *snowflakeConn, credType, token string) { +- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) +- credCacheLock.Lock() +- defer credCacheLock.Unlock() +- localCredCache[target] = token +- +- j, err := json.Marshal(localCredCache) +- if err != nil { +- logger.Warnf("failed to convert credential to JSON.") +- return +- } +- writeTemporaryCacheFile(j) ++type noopSecureStorageManager struct { + } + +-func deleteTemporaryCredential(sc *snowflakeConn, credType string) { +- if credCacheDir == "" { +- logger.Debug("Cache file doesn't exist. Skipping deleting credential file.") +- } else { +- credCacheLock.Lock() +- defer credCacheLock.Unlock() +- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) +- delete(localCredCache, target) +- j, err := json.Marshal(localCredCache) +- if err != nil { +- logger.Warnf("failed to convert credential to JSON.") +- return +- } +- writeTemporaryCacheFile(j) +- } ++func newNoopSecureStorageManager() secureStorageManager { ++ return &noopSecureStorageManager{} + } + +-func readTemporaryCacheFile() map[string]string { +- if credCache == "" { +- logger.Debug("Cache file doesn't exist. Skipping reading credential file.") +- return nil +- } +- jsonData, err := os.ReadFile(credCache) +- if err != nil { +- logger.Debugf("Failed to read credential file: %v", err) +- return nil +- } +- err = json.Unmarshal([]byte(jsonData), &localCredCache) +- if err != nil { +- logger.Debugf("failed to read JSON. Err: %v", err) +- return nil +- } +- +- return localCredCache ++func (ssm *noopSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { + } + +-func writeTemporaryCacheFile(input []byte) { +- if credCache == "" { +- logger.Debug("Cache file doesn't exist. Skipping writing temporary credential file.") +- } else { +- logger.Debugf("writing credential cache file. %v\n", credCache) +- credCacheLockFileName := credCache + ".lck" +- err := os.Mkdir(credCacheLockFileName, 0600) +- logger.Debugf("Creating lock file. %v", credCacheLockFileName) +- +- switch { +- case os.IsExist(err): +- statinfo, err := os.Stat(credCacheLockFileName) +- if err != nil { +- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", credCache, err) +- return +- } +- if time.Since(statinfo.ModTime()) < 15*time.Minute { +- logger.Debugf("other process locks the cache file. %v. ignored.\n", credCache) +- return +- } +- if err = os.Remove(credCacheLockFileName); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return +- } +- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return +- } +- } +- defer os.RemoveAll(credCacheLockFileName) +- +- if err = os.WriteFile(credCache, input, 0644); err != nil { +- logger.Debugf("Failed to write the cache file. File: %v err: %v.", credCache, err) +- } +- } ++func (ssm *noopSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { + } + +-func convertTarget(host, user, credType string) string { +- host = strings.ToUpper(host) +- user = strings.ToUpper(user) +- credType = strings.ToUpper(credType) +- target := host + ":" + user + ":" + driverName + ":" + credType +- return target ++func (ssm *noopSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { //TODO implement me + } +-- +2.34.1 + + +From 207a4e0397e893cc8d90e3ce551d36b11f1c537d Mon Sep 17 00:00:00 2001 +From: Kevin Lockwood +Date: Wed, 25 Jun 2025 16:39:09 -0700 +Subject: [PATCH 2/4] SNOW-1825790 Token cache refactor - v2 (#1299) + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/d8df82efb12b0428a88dfe4ec80e3b0936569d64.patch +--- + .../snowflakedb/gosnowflake/auth.go | 22 +-- + .../gosnowflake/secure_storage_manager.go | 146 ++++++++++-------- + 2 files changed, 88 insertions(+), 80 deletions(-) + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/auth.go b/vendor/github.com/snowflakedb/gosnowflake/auth.go +index 888cd1ba..f2948475 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/auth.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/auth.go +@@ -25,8 +25,6 @@ const ( + ) + + const ( +- idToken = "ID_TOKEN" +- mfaToken = "MFATOKEN" + clientStoreTemporaryCredential = "CLIENT_STORE_TEMPORARY_CREDENTIAL" + clientRequestMfaToken = "CLIENT_REQUEST_MFA_TOKEN" + idTokenAuthenticator = "ID_TOKEN" +@@ -364,10 +362,10 @@ func authenticate( + logger.WithContext(ctx).Errorln("Authentication FAILED") + sc.rest.TokenAccessor.SetTokens("", "", -1) + if sessionParameters[clientRequestMfaToken] == true { +- credentialsStorage.deleteCredential(sc, mfaToken) ++ credentialsStorage.deleteCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) + } + if sessionParameters[clientStoreTemporaryCredential] == true { +- credentialsStorage.deleteCredential(sc, idToken) ++ credentialsStorage.deleteCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) + } + code, err := strconv.Atoi(respd.Code) + if err != nil { +@@ -384,11 +382,11 @@ func authenticate( + sc.rest.TokenAccessor.SetTokens(respd.Data.Token, respd.Data.MasterToken, respd.Data.SessionID) + if sessionParameters[clientRequestMfaToken] == true { + token := respd.Data.MfaToken +- credentialsStorage.setCredential(sc, mfaToken, token) ++ credentialsStorage.setCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User), token) + } + if sessionParameters[clientStoreTemporaryCredential] == true { + token := respd.Data.IDToken +- credentialsStorage.setCredential(sc, idToken, token) ++ credentialsStorage.setCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User), token) + } + return &respd.Data, nil + } +@@ -513,7 +511,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { + sc.cfg.ClientStoreTemporaryCredential = ConfigBoolTrue + } + if sc.cfg.ClientStoreTemporaryCredential == ConfigBoolTrue { +- fillCachedIDToken(sc) ++ sc.cfg.IDToken = credentialsStorage.getCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) + } + // Disable console login by default + if sc.cfg.DisableConsoleLogin == configBoolNotSet { +@@ -526,7 +524,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { + sc.cfg.ClientRequestMfaToken = ConfigBoolTrue + } + if sc.cfg.ClientRequestMfaToken == ConfigBoolTrue { +- fillCachedMfaToken(sc) ++ sc.cfg.MfaToken = credentialsStorage.getCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) + } + } + +@@ -563,11 +561,3 @@ func authenticateWithConfig(sc *snowflakeConn) error { + sc.ctx = context.WithValue(sc.ctx, SFSessionIDKey, authData.SessionID) + return nil + } +- +-func fillCachedIDToken(sc *snowflakeConn) { +- credentialsStorage.getCredential(sc, idToken) +-} +- +-func fillCachedMfaToken(sc *snowflakeConn) { +- credentialsStorage.getCredential(sc, mfaToken) +-} +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index fc392662..6be71a57 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -15,16 +15,48 @@ import ( + "github.com/99designs/keyring" + ) + ++type tokenType string ++ ++const ( ++ idToken tokenType = "ID_TOKEN" ++ mfaToken tokenType = "MFATOKEN" ++) ++ + const ( + driverName = "SNOWFLAKE-GO-DRIVER" + credCacheDirEnv = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR" + credCacheFileName = "temporary_credential.json" + ) + ++type secureTokenSpec struct { ++ host, user string ++ tokenType tokenType ++} ++ ++func (t *secureTokenSpec) buildKey() string { ++ return buildCredentialsKey(t.host, t.user, t.tokenType) ++} ++ ++func newMfaTokenSpec(host, user string) *secureTokenSpec { ++ return &secureTokenSpec{ ++ host, ++ user, ++ mfaToken, ++ } ++} ++ ++func newIDTokenSpec(host, user string) *secureTokenSpec { ++ return &secureTokenSpec{ ++ host, ++ user, ++ idToken, ++ } ++} ++ + type secureStorageManager interface { +- setCredential(sc *snowflakeConn, credType, token string) +- getCredential(sc *snowflakeConn, credType string) +- deleteCredential(sc *snowflakeConn, credType string) ++ setCredential(tokenSpec *secureTokenSpec, value string) ++ getCredential(tokenSpec *secureTokenSpec) string ++ deleteCredential(tokenSpec *secureTokenSpec) + } + + var credentialsStorage = newSecureStorageManager() +@@ -32,7 +64,12 @@ var credentialsStorage = newSecureStorageManager() + func newSecureStorageManager() secureStorageManager { + switch runtime.GOOS { + case "linux": +- return newFileBasedSecureStorageManager() ++ ssm, err := newFileBasedSecureStorageManager() ++ if err != nil { ++ logger.Debugf("failed to create credentials cache dir. %v", err) ++ return newNoopSecureStorageManager() ++ } ++ return ssm + case "darwin", "windows": + return newKeyringBasedSecureStorageManager() + default: +@@ -46,20 +83,19 @@ type fileBasedSecureStorageManager struct { + credCacheLock sync.RWMutex + } + +-func newFileBasedSecureStorageManager() secureStorageManager { ++func newFileBasedSecureStorageManager() (*fileBasedSecureStorageManager, error) { + ssm := &fileBasedSecureStorageManager{ + localCredCache: map[string]string{}, + credCacheLock: sync.RWMutex{}, + } + credCacheDir := ssm.buildCredCacheDirPath() + if err := ssm.createCacheDir(credCacheDir); err != nil { +- logger.Debugf("failed to create credentials cache dir. %v", err) +- return newNoopSecureStorageManager() ++ return nil, err + } + credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) + logger.Infof("Credentials cache path: %v", credCacheFilePath) + ssm.credCacheFilePath = credCacheFilePath +- return ssm ++ return ssm, nil + } + + func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { +@@ -87,14 +123,14 @@ func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { + return credCacheDir + } + +-func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { +- if token == "" { ++func (ssm *fileBasedSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { ++ if value == "" { + logger.Debug("no token provided") + } else { +- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey := tokenSpec.buildKey() + ssm.credCacheLock.Lock() + defer ssm.credCacheLock.Unlock() +- ssm.localCredCache[credentialsKey] = token ++ ssm.localCredCache[credentialsKey] = value + + j, err := json.Marshal(ssm.localCredCache) + if err != nil { +@@ -135,8 +171,8 @@ func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credT + } + } + +-func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { +- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++func (ssm *fileBasedSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { ++ credentialsKey := tokenSpec.buildKey() + ssm.credCacheLock.Lock() + defer ssm.credCacheLock.Unlock() + localCredCache := ssm.readTemporaryCacheFile() +@@ -146,14 +182,7 @@ func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credT + } else { + logger.Debug("Returned credential is empty") + } +- +- if credType == idToken { +- sc.cfg.IDToken = cred +- } else if credType == mfaToken { +- sc.cfg.MfaToken = cred +- } else { +- logger.Debugf("Unrecognized type %v for local cached credential", credType) +- } ++ return cred + } + + func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { +@@ -171,10 +200,10 @@ func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]st + return ssm.localCredCache + } + +-func (ssm *fileBasedSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { ++func (ssm *fileBasedSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { + ssm.credCacheLock.Lock() + defer ssm.credCacheLock.Unlock() +- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) ++ credentialsKey := tokenSpec.buildKey() + delete(ssm.localCredCache, credentialsKey) + j, err := json.Marshal(ssm.localCredCache) + if err != nil { +@@ -220,37 +249,35 @@ func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) + type keyringSecureStorageManager struct { + } + +-func newKeyringBasedSecureStorageManager() secureStorageManager { ++func newKeyringBasedSecureStorageManager() *keyringSecureStorageManager { + return &keyringSecureStorageManager{} + } + +-func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { +- if token == "" { ++func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { ++ if value == "" { + logger.Debug("no token provided") + } else { +- var credentialsKey string ++ credentialsKey := tokenSpec.buildKey() + if runtime.GOOS == "windows" { +- credentialsKey = driverName + ":" + credType + ring, _ := keyring.Open(keyring.Config{ +- WinCredPrefix: strings.ToUpper(sc.cfg.Host), +- ServiceName: strings.ToUpper(sc.cfg.User), ++ WinCredPrefix: strings.ToUpper(tokenSpec.host), ++ ServiceName: strings.ToUpper(tokenSpec.user), + }) + item := keyring.Item{ + Key: credentialsKey, +- Data: []byte(token), ++ Data: []byte(value), + } + if err := ring.Set(item); err != nil { + logger.Debugf("Failed to write to Windows credential manager. Err: %v", err) + } + } else if runtime.GOOS == "darwin" { +- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ + ServiceName: credentialsKey, + }) +- account := strings.ToUpper(sc.cfg.User) ++ account := strings.ToUpper(tokenSpec.user) + item := keyring.Item{ + Key: account, +- Data: []byte(token), ++ Data: []byte(value), + } + if err := ring.Set(item); err != nil { + logger.Debugf("Failed to write to keychain. Err: %v", err) +@@ -259,14 +286,13 @@ func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credTyp + } + } + +-func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { +- var credentialsKey string ++func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { + cred := "" ++ credentialsKey := tokenSpec.buildKey() + if runtime.GOOS == "windows" { +- credentialsKey = driverName + ":" + credType + ring, _ := keyring.Open(keyring.Config{ +- WinCredPrefix: strings.ToUpper(sc.cfg.Host), +- ServiceName: strings.ToUpper(sc.cfg.User), ++ WinCredPrefix: strings.ToUpper(tokenSpec.host), ++ ServiceName: strings.ToUpper(tokenSpec.user), + }) + i, err := ring.Get(credentialsKey) + if err != nil { +@@ -274,11 +300,10 @@ func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credTyp + } + cred = string(i.Data) + } else if runtime.GOOS == "darwin" { +- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ + ServiceName: credentialsKey, + }) +- account := strings.ToUpper(sc.cfg.User) ++ account := strings.ToUpper(tokenSpec.user) + i, err := ring.Get(account) + if err != nil { + logger.Debugf("Failed to find the item in keychain or item does not exist. Error: %v", err) +@@ -290,33 +315,25 @@ func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credTyp + logger.Debug("Successfully read token. Returning as string") + } + } +- +- if credType == idToken { +- sc.cfg.IDToken = cred +- } else if credType == mfaToken { +- sc.cfg.MfaToken = cred +- } else { +- logger.Debugf("Unrecognized type %v for local cached credential", credType) +- } ++ return cred + } + +-func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { +- credentialsKey := driverName + ":" + credType ++func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { ++ credentialsKey := tokenSpec.buildKey() + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ +- WinCredPrefix: strings.ToUpper(sc.cfg.Host), +- ServiceName: strings.ToUpper(sc.cfg.User), ++ WinCredPrefix: strings.ToUpper(tokenSpec.host), ++ ServiceName: strings.ToUpper(tokenSpec.user), + }) +- err := ring.Remove(credentialsKey) ++ err := ring.Remove(string(credentialsKey)) + if err != nil { + logger.Debugf("Failed to delete credentialsKey in Windows Credential Manager. Error: %v", err) + } + } else if runtime.GOOS == "darwin" { +- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) + ring, _ := keyring.Open(keyring.Config{ + ServiceName: credentialsKey, + }) +- account := strings.ToUpper(sc.cfg.User) ++ account := strings.ToUpper(tokenSpec.user) + err := ring.Remove(account) + if err != nil { + logger.Debugf("Failed to delete credentialsKey in keychain. Error: %v", err) +@@ -324,25 +341,26 @@ func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, cred + } + } + +-func buildCredentialsKey(host, user, credType string) string { ++func buildCredentialsKey(host, user string, credType tokenType) string { + host = strings.ToUpper(host) + user = strings.ToUpper(user) +- credType = strings.ToUpper(credType) +- return host + ":" + user + ":" + driverName + ":" + credType ++ credTypeStr := strings.ToUpper(string(credType)) ++ return host + ":" + user + ":" + driverName + ":" + credTypeStr + } + + type noopSecureStorageManager struct { + } + +-func newNoopSecureStorageManager() secureStorageManager { ++func newNoopSecureStorageManager() *noopSecureStorageManager { + return &noopSecureStorageManager{} + } + +-func (ssm *noopSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { ++func (ssm *noopSecureStorageManager) setCredential(_ *secureTokenSpec, _ string) { + } + +-func (ssm *noopSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { ++func (ssm *noopSecureStorageManager) getCredential(_ *secureTokenSpec) string { ++ return "" + } + +-func (ssm *noopSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { //TODO implement me ++func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { //TODO implement me + } +-- +2.34.1 + + +From 77a81aa504d9729edc50fab0061ef36dda7dfc82 Mon Sep 17 00:00:00 2001 +From: Kevin Lockwood +Date: Wed, 25 Jun 2025 16:40:59 -0700 +Subject: [PATCH 3/4] SNOW-1825790 Implement safer file based token cache + (#1327) + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/61b822d82822f9672b6749345bf64a044a0532bf.patch +--- + .../gosnowflake/os_specific_posix.go | 25 + + .../gosnowflake/os_specific_windows.go | 12 + + .../gosnowflake/secure_storage_manager.go | 463 +++++++++++++----- + 3 files changed, 370 insertions(+), 130 deletions(-) + create mode 100644 vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go + create mode 100644 vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go b/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go +new file mode 100644 +index 00000000..2d01f5c0 +--- /dev/null ++++ b/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go +@@ -0,0 +1,25 @@ ++//go:build darwin || linux ++ ++package gosnowflake ++ ++import ( ++ "fmt" ++ "os" ++ "syscall" ++) ++ ++func provideFileOwner(file *os.File) (uint32, error) { ++ info, err := file.Stat() ++ if err != nil { ++ return 0, err ++ } ++ return provideOwnerFromStat(info, file.Name()) ++} ++ ++func provideOwnerFromStat(info os.FileInfo, filepath string) (uint32, error) { ++ nativeStat, ok := info.Sys().(*syscall.Stat_t) ++ if !ok { ++ return 0, fmt.Errorf("cannot cast file info for %v to *syscall.Stat_t", filepath) ++ } ++ return nativeStat.Uid, nil ++} +diff --git a/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go b/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go +new file mode 100644 +index 00000000..293123e0 +--- /dev/null ++++ b/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go +@@ -0,0 +1,12 @@ ++// go:build windows ++ ++package gosnowflake ++ ++import ( ++ "errors" ++ "os" ++) ++ ++func provideFileOwner(file *os.File) (uint32, error) { ++ return 0, errors.New("provideFileOwner is unsupported on windows") ++} +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index 6be71a57..ab33874e 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -3,16 +3,21 @@ + package gosnowflake + + import ( ++ "crypto/sha256" ++ "encoding/hex" + "encoding/json" ++ "errors" + "fmt" ++ "github.com/99designs/keyring" ++ "io" + "os" ++ "os/user" + "path/filepath" + "runtime" ++ "strconv" + "strings" + "sync" + "time" +- +- "github.com/99designs/keyring" + ) + + type tokenType string +@@ -23,17 +28,27 @@ const ( + ) + + const ( +- driverName = "SNOWFLAKE-GO-DRIVER" + credCacheDirEnv = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR" +- credCacheFileName = "temporary_credential.json" ++ credCacheFileName = "credential_cache_v1.json" + ) + ++type cacheDirConf struct { ++ envVar string ++ pathSegments []string ++} ++ ++var defaultLinuxCacheDirConf = []cacheDirConf{ ++ {envVar: credCacheDirEnv, pathSegments: []string{}}, ++ {envVar: "XDG_CACHE_DIR", pathSegments: []string{"snowflake"}}, ++ {envVar: "HOME", pathSegments: []string{".cache", "snowflake"}}, ++} ++ + type secureTokenSpec struct { + host, user string + tokenType tokenType + } + +-func (t *secureTokenSpec) buildKey() string { ++func (t *secureTokenSpec) buildKey() (string, error) { + return buildCredentialsKey(t.host, t.user, t.tokenType) + } + +@@ -69,181 +84,328 @@ func newSecureStorageManager() secureStorageManager { + logger.Debugf("failed to create credentials cache dir. %v", err) + return newNoopSecureStorageManager() + } +- return ssm ++ return &threadSafeSecureStorageManager{&sync.Mutex{}, ssm} + case "darwin", "windows": +- return newKeyringBasedSecureStorageManager() ++ return &threadSafeSecureStorageManager{&sync.Mutex{}, newKeyringBasedSecureStorageManager()} + default: ++ logger.Warnf("OS %v does not support credentials cache", runtime.GOOS) + return newNoopSecureStorageManager() + } + } + + type fileBasedSecureStorageManager struct { +- credCacheFilePath string +- localCredCache map[string]string +- credCacheLock sync.RWMutex ++ credDirPath string + } + + func newFileBasedSecureStorageManager() (*fileBasedSecureStorageManager, error) { +- ssm := &fileBasedSecureStorageManager{ +- localCredCache: map[string]string{}, +- credCacheLock: sync.RWMutex{}, +- } +- credCacheDir := ssm.buildCredCacheDirPath() +- if err := ssm.createCacheDir(credCacheDir); err != nil { ++ credDirPath, err := buildCredCacheDirPath(defaultLinuxCacheDirConf) ++ if err != nil { + return nil, err + } +- credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) +- logger.Infof("Credentials cache path: %v", credCacheFilePath) +- ssm.credCacheFilePath = credCacheFilePath ++ ssm := &fileBasedSecureStorageManager{ ++ credDirPath: credDirPath, ++ } + return ssm, nil + } + +-func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { +- _, err := os.Stat(credCacheDir) +- if os.IsNotExist(err) { +- if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { +- return fmt.Errorf("failed to create cache directory. %v, err: %v", credCacheDir, err) ++func lookupCacheDir(envVar string, pathSegments ...string) (string, error) { ++ envVal := os.Getenv(envVar) ++ if envVal == "" { ++ return "", fmt.Errorf("environment variable %s not set", envVar) ++ } ++ ++ fileInfo, err := os.Stat(envVal) ++ if err != nil { ++ return "", fmt.Errorf("failed to stat %s=%s, due to %v", envVar, envVal, err) ++ } ++ ++ if !fileInfo.IsDir() { ++ return "", fmt.Errorf("environment variable %s=%s is not a directory", envVar, envVal) ++ } ++ ++ cacheDir := filepath.Join(envVal, filepath.Join(pathSegments...)) ++ parentOfCacheDir := cacheDir[:strings.LastIndex(cacheDir, "/")] ++ ++ if err = os.MkdirAll(parentOfCacheDir, os.FileMode(0755)); err != nil { ++ return "", err ++ } ++ ++ // We don't check if permissions are incorrect here if a directory exists, because we check it later. ++ if err = os.Mkdir(cacheDir, os.FileMode(0700)); err != nil && !errors.Is(err, os.ErrExist) { ++ return "", err ++ } ++ ++ return cacheDir, nil ++} ++ ++func buildCredCacheDirPath(confs []cacheDirConf) (string, error) { ++ for _, conf := range confs { ++ path, err := lookupCacheDir(conf.envVar, conf.pathSegments...) ++ if err != nil { ++ logger.Debugf("Skipping %s in cache directory lookup due to %v", conf.envVar, err) ++ } else { ++ logger.Debugf("Using %s as cache directory", path) ++ return path, nil + } +- return nil + } +- return err ++ ++ return "", errors.New("no credentials cache directory found") + } + +-func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { +- credCacheDir := os.Getenv(credCacheDirEnv) +- if credCacheDir != "" { +- return credCacheDir ++func (ssm *fileBasedSecureStorageManager) getTokens(data map[string]any) map[string]interface{} { ++ val, ok := data["tokens"] ++ if !ok { ++ return map[string]interface{}{} + } +- home := os.Getenv("HOME") +- if home == "" { +- logger.Info("HOME is blank") +- return "" ++ ++ tokens, ok := val.(map[string]interface{}) ++ if !ok { ++ return map[string]interface{}{} + } +- credCacheDir = filepath.Join(home, ".cache", "snowflake") +- return credCacheDir ++ ++ return tokens ++} ++ ++func (ssm *fileBasedSecureStorageManager) withLock(action func(cacheFile *os.File)) { ++ err := ssm.lockFile() ++ if err != nil { ++ logger.Warnf("Unable to lock cache. %v", err) ++ return ++ } ++ defer ssm.unlockFile() ++ ++ ssm.withCacheFile(action) ++} ++ ++func (ssm *fileBasedSecureStorageManager) withCacheFile(action func(*os.File)) { ++ cacheFile, err := os.OpenFile(ssm.credFilePath(), os.O_CREATE|os.O_RDWR, 0600) ++ if err != nil { ++ logger.Warnf("cannot access %v. %v", ssm.credFilePath(), err) ++ return ++ } ++ defer func(file *os.File) { ++ if err := file.Close(); err != nil { ++ logger.Warnf("cannot release file descriptor for %v. %v", ssm.credFilePath(), err) ++ } ++ }(cacheFile) ++ ++ cacheDir, err := os.Open(ssm.credDirPath) ++ if err != nil { ++ logger.Warnf("cannot access %v. %v", ssm.credDirPath, err) ++ } ++ defer func(file *os.File) { ++ if err := file.Close(); err != nil { ++ logger.Warnf("cannot release file descriptor for %v. %v", cacheDir, err) ++ } ++ }(cacheDir) ++ ++ if err := ssm.ensurePermissionsAndOwner(cacheFile, 0600); err != nil { ++ logger.Warnf("failed to ensure permission for temporary cache file. %v", err) ++ return ++ } ++ if err := ssm.ensurePermissionsAndOwner(cacheDir, 0700|os.ModeDir); err != nil { ++ logger.Warnf("failed to ensure permission for temporary cache dir. %v", err) ++ return ++ } ++ ++ action(cacheFile) + } + + func (ssm *fileBasedSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { +- if value == "" { +- logger.Debug("no token provided") +- } else { +- credentialsKey := tokenSpec.buildKey() +- ssm.credCacheLock.Lock() +- defer ssm.credCacheLock.Unlock() +- ssm.localCredCache[credentialsKey] = value ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return ++ } + +- j, err := json.Marshal(ssm.localCredCache) ++ ssm.withLock(func(cacheFile *os.File) { ++ credCache, err := ssm.readTemporaryCacheFile(cacheFile) + if err != nil { +- logger.Warnf("failed to convert credential to JSON.") ++ logger.Warnf("Error while reading cache file. %v", err) + return + } ++ tokens := ssm.getTokens(credCache) ++ tokens[credentialsKey] = value ++ credCache["tokens"] = tokens ++ err = ssm.writeTemporaryCacheFile(credCache, cacheFile) ++ if err != nil { ++ logger.Warnf("Set credential failed. Unable to write cache. %v", err) ++ } ++ }) ++} + +- logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) +- credCacheLockFileName := ssm.credCacheFilePath + ".lck" +- logger.Debugf("Creating lock file. %v", credCacheLockFileName) +- err = os.Mkdir(credCacheLockFileName, 0600) +- +- switch { +- case os.IsExist(err): +- statinfo, err := os.Stat(credCacheLockFileName) +- if err != nil { +- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) +- return +- } +- if time.Since(statinfo.ModTime()) < 15*time.Minute { +- logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) +- return +- } +- if err = os.Remove(credCacheLockFileName); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return +- } +- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return +- } ++func (ssm *fileBasedSecureStorageManager) lockPath() string { ++ return filepath.Join(ssm.credDirPath, credCacheFileName+".lck") ++} ++ ++func (ssm *fileBasedSecureStorageManager) lockFile() error { ++ const numRetries = 10 ++ const retryInterval = 100 * time.Millisecond ++ lockPath := ssm.lockPath() ++ ++ fileInfo, err := os.Stat(lockPath) ++ if err != nil && !errors.Is(err, os.ErrNotExist) { ++ return fmt.Errorf("failed to stat %v and determine if lock is stale. err: %v", lockPath, err) ++ } ++ ++ // removing stale lock ++ now := time.Now() ++ if !errors.Is(err, os.ErrNotExist) && fileInfo.ModTime().Add(time.Second).UnixNano() < now.UnixNano() { ++ logger.Debugf("removing credentials cache lock file, stale for %vms", (now.UnixNano()-fileInfo.ModTime().UnixNano())/1000/1000) ++ err = os.Remove(lockPath) ++ if err != nil { ++ return fmt.Errorf("failed to remove %v while trying to remove stale lock. err: %v", lockPath, err) + } +- defer os.RemoveAll(credCacheLockFileName) ++ } + +- if err = os.WriteFile(ssm.credCacheFilePath, j, 0644); err != nil { +- logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) ++ locked := false ++ for i := 0; i < numRetries; i++ { ++ err := os.Mkdir(lockPath, 0700) ++ if err != nil { ++ if errors.Is(err, os.ErrExist) { ++ time.Sleep(retryInterval) ++ continue ++ } ++ return fmt.Errorf("failed to create cache lock: %v, err: %v", lockPath, err) + } ++ locked = true ++ break ++ } ++ if !locked { ++ return fmt.Errorf("failed to lock cache. lockPath: %v", lockPath) ++ } ++ return nil ++} ++ ++func (ssm *fileBasedSecureStorageManager) unlockFile() { ++ lockPath := ssm.lockPath() ++ err := os.Remove(lockPath) ++ if err != nil { ++ logger.Warnf("Failed to unlock cache lock: %v. %v", lockPath, err) + } + } + + func (ssm *fileBasedSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { +- credentialsKey := tokenSpec.buildKey() +- ssm.credCacheLock.Lock() +- defer ssm.credCacheLock.Unlock() +- localCredCache := ssm.readTemporaryCacheFile() +- cred := localCredCache[credentialsKey] +- if cred != "" { +- logger.Debug("Successfully read token. Returning as string") +- } else { +- logger.Debug("Returned credential is empty") ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return "" + } +- return cred ++ ++ ret := "" ++ ssm.withLock(func(cacheFile *os.File) { ++ credCache, err := ssm.readTemporaryCacheFile(cacheFile) ++ if err != nil { ++ logger.Warnf("Error while reading cache file. %v", err) ++ return ++ } ++ cred, ok := ssm.getTokens(credCache)[credentialsKey] ++ if !ok { ++ return ++ } ++ ++ credStr, ok := cred.(string) ++ if !ok { ++ return ++ } ++ ++ ret = credStr ++ }) ++ return ret + } + +-func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { +- jsonData, err := os.ReadFile(ssm.credCacheFilePath) ++func (ssm *fileBasedSecureStorageManager) credFilePath() string { ++ return filepath.Join(ssm.credDirPath, credCacheFileName) ++} ++ ++func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, expectedMode os.FileMode) error { ++ fileInfo, err := f.Stat() + if err != nil { +- logger.Debugf("Failed to read credential file: %v", err) +- return nil ++ return err ++ } ++ ++ if fileInfo.Mode().Perm() != expectedMode&os.ModePerm { ++ return fmt.Errorf("incorrect permissions(%v, expected %v) for credential file", fileInfo.Mode(), expectedMode) ++ } ++ ++ ownerUID, err := provideFileOwner(f) ++ if err != nil && !errors.Is(err, os.ErrNotExist) { ++ return err + } +- err = json.Unmarshal([]byte(jsonData), &ssm.localCredCache) ++ currentUser, err := user.Current() + if err != nil { +- logger.Debugf("failed to read JSON. Err: %v", err) ++ return err ++ } ++ if errors.Is(err, os.ErrNotExist) { + return nil + } ++ if strconv.Itoa(int(ownerUID)) != currentUser.Uid { ++ return errors.New("incorrect owner of " + ssm.credDirPath) ++ } ++ return nil ++} ++ ++func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile(cacheFile *os.File) (map[string]any, error) { ++ ++ jsonData, err := io.ReadAll(cacheFile) ++ if err != nil { ++ logger.Warnf("Failed to read credential cache file. %v.\n", err) ++ return map[string]any{}, nil ++ } ++ if _, err = cacheFile.Seek(0, 0); err != nil { ++ return map[string]any{}, fmt.Errorf("cannot seek to the beginning of a cache file. %v", err) ++ } ++ ++ if len(jsonData) == 0 { ++ // Happens when the file didn't exist before. ++ return map[string]any{}, nil ++ } + +- return ssm.localCredCache ++ credentialsMap := map[string]any{} ++ err = json.Unmarshal(jsonData, &credentialsMap) ++ if err != nil { ++ return map[string]any{}, fmt.Errorf("failed to unmarshal credential cache file. %v", err) ++ } ++ ++ return credentialsMap, nil + } + + func (ssm *fileBasedSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { +- ssm.credCacheLock.Lock() +- defer ssm.credCacheLock.Unlock() +- credentialsKey := tokenSpec.buildKey() +- delete(ssm.localCredCache, credentialsKey) +- j, err := json.Marshal(ssm.localCredCache) ++ credentialsKey, err := tokenSpec.buildKey() + if err != nil { +- logger.Warnf("failed to convert credential to JSON.") ++ logger.Warn(err) + return + } +- ssm.writeTemporaryCacheFile(j) +-} + +-func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) { +- logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) +- credCacheLockFileName := ssm.credCacheFilePath + ".lck" +- err := os.Mkdir(credCacheLockFileName, 0600) +- logger.Debugf("Creating lock file. %v", credCacheLockFileName) +- +- switch { +- case os.IsExist(err): +- statinfo, err := os.Stat(credCacheLockFileName) ++ ssm.withLock(func(cacheFile *os.File) { ++ credCache, err := ssm.readTemporaryCacheFile(cacheFile) + if err != nil { +- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) +- return +- } +- if time.Since(statinfo.ModTime()) < 15*time.Minute { +- logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) +- return +- } +- if err = os.Remove(credCacheLockFileName); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) ++ logger.Warnf("Error while reading cache file. %v", err) + return + } +- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { +- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) +- return ++ delete(ssm.getTokens(credCache), credentialsKey) ++ ++ err = ssm.writeTemporaryCacheFile(credCache, cacheFile) ++ if err != nil { ++ logger.Warnf("Set credential failed. Unable to write cache. %v", err) + } ++ }) ++} ++ ++func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(cache map[string]any, cacheFile *os.File) error { ++ bytes, err := json.Marshal(cache) ++ if err != nil { ++ return fmt.Errorf("failed to marshal credential cache map. %w", err) + } +- defer os.RemoveAll(credCacheLockFileName) + +- if err = os.WriteFile(ssm.credCacheFilePath, input, 0644); err != nil { +- logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) ++ if err = cacheFile.Truncate(0); err != nil { ++ return fmt.Errorf("error while truncating credentials cache. %v", err) ++ } ++ _, err = cacheFile.Write(bytes) ++ if err != nil { ++ return fmt.Errorf("failed to write the credential cache file: %w", err) + } ++ return nil + } + + type keyringSecureStorageManager struct { +@@ -257,7 +419,11 @@ func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec + if value == "" { + logger.Debug("no token provided") + } else { +- credentialsKey := tokenSpec.buildKey() ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return ++ } + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(tokenSpec.host), +@@ -288,7 +454,11 @@ func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec + + func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { + cred := "" +- credentialsKey := tokenSpec.buildKey() ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return "" ++ } + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(tokenSpec.host), +@@ -319,7 +489,11 @@ func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec + } + + func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { +- credentialsKey := tokenSpec.buildKey() ++ credentialsKey, err := tokenSpec.buildKey() ++ if err != nil { ++ logger.Warn(err) ++ return ++ } + if runtime.GOOS == "windows" { + ring, _ := keyring.Open(keyring.Config{ + WinCredPrefix: strings.ToUpper(tokenSpec.host), +@@ -341,11 +515,17 @@ func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenS + } + } + +-func buildCredentialsKey(host, user string, credType tokenType) string { +- host = strings.ToUpper(host) +- user = strings.ToUpper(user) +- credTypeStr := strings.ToUpper(string(credType)) +- return host + ":" + user + ":" + driverName + ":" + credTypeStr ++func buildCredentialsKey(host, user string, credType tokenType) (string, error) { ++ if host == "" { ++ return "", errors.New("host is not provided to store in token cache, skipping") ++ } ++ if user == "" { ++ return "", errors.New("user is not provided to store in token cache, skipping") ++ } ++ plainCredKey := host + ":" + user + ":" + string(credType) ++ checksum := sha256.New() ++ checksum.Write([]byte(plainCredKey)) ++ return hex.EncodeToString(checksum.Sum(nil)), nil + } + + type noopSecureStorageManager struct { +@@ -362,5 +542,28 @@ func (ssm *noopSecureStorageManager) getCredential(_ *secureTokenSpec) string { + return "" + } + +-func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { //TODO implement me ++func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { ++} ++ ++type threadSafeSecureStorageManager struct { ++ mu *sync.Mutex ++ delegate secureStorageManager ++} ++ ++func (ssm *threadSafeSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { ++ ssm.mu.Lock() ++ defer ssm.mu.Unlock() ++ ssm.delegate.setCredential(tokenSpec, value) ++} ++ ++func (ssm *threadSafeSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { ++ ssm.mu.Lock() ++ defer ssm.mu.Unlock() ++ return ssm.delegate.getCredential(tokenSpec) ++} ++ ++func (ssm *threadSafeSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { ++ ssm.mu.Lock() ++ defer ssm.mu.Unlock() ++ ssm.delegate.deleteCredential(tokenSpec) + } +-- +2.34.1 + + +From e8e6d49a988ac24c0691434b3961911f091e0a90 Mon Sep 17 00:00:00 2001 +From: Piotr Fus +Date: Tue, 1 Apr 2025 10:55:30 +0200 +Subject: [PATCH 4/4] =?UTF-8?q?SNOW-1825790=20Fix=20error=20message=20when?= + =?UTF-8?q?=20credentials=20file=20owner=20does=20not=20m=E2=80=A6=20(#134?= + =?UTF-8?q?9)?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/40e4f5c874dcc003f6318a355b66da8056db5ce4.patch +--- + .../snowflakedb/gosnowflake/secure_storage_manager.go | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +index ab33874e..a823a52e 100644 +--- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go ++++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go +@@ -340,7 +340,7 @@ func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, + return nil + } + if strconv.Itoa(int(ownerUID)) != currentUser.Uid { +- return errors.New("incorrect owner of " + ssm.credDirPath) ++ return errors.New("incorrect owner of " + f.Name()) + } + return nil + } +-- +2.34.1 + diff --git a/SPECS/telegraf/CVE-2025-46327.patch b/SPECS/telegraf/CVE-2025-46327.patch index 6998ae5eaa6..481d4386f2b 100644 --- a/SPECS/telegraf/CVE-2025-46327.patch +++ b/SPECS/telegraf/CVE-2025-46327.patch @@ -1,1644 +1,7 @@ -From 2f9da773e6e0ea2e7653b0a379304c09e4422a0a Mon Sep 17 00:00:00 2001 -From: Kevin Lockwood -Date: Wed, 25 Jun 2025 16:38:08 -0700 -Subject: [PATCH 1/5] SNOW-1825790 Separate secure storage managers based on - file and keyring (#1295) - -Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/e926883b12016eb30ae25c2027f2cf70714eb2c5.patch ---- - .../snowflakedb/gosnowflake/auth.go | 12 +- - .../gosnowflake/secure_storage_manager.go | 384 ++++++++++-------- - 2 files changed, 230 insertions(+), 166 deletions(-) - -diff --git a/vendor/github.com/snowflakedb/gosnowflake/auth.go b/vendor/github.com/snowflakedb/gosnowflake/auth.go -index 6d069a91..888cd1ba 100644 ---- a/vendor/github.com/snowflakedb/gosnowflake/auth.go -+++ b/vendor/github.com/snowflakedb/gosnowflake/auth.go -@@ -364,10 +364,10 @@ func authenticate( - logger.WithContext(ctx).Errorln("Authentication FAILED") - sc.rest.TokenAccessor.SetTokens("", "", -1) - if sessionParameters[clientRequestMfaToken] == true { -- deleteCredential(sc, mfaToken) -+ credentialsStorage.deleteCredential(sc, mfaToken) - } - if sessionParameters[clientStoreTemporaryCredential] == true { -- deleteCredential(sc, idToken) -+ credentialsStorage.deleteCredential(sc, idToken) - } - code, err := strconv.Atoi(respd.Code) - if err != nil { -@@ -384,11 +384,11 @@ func authenticate( - sc.rest.TokenAccessor.SetTokens(respd.Data.Token, respd.Data.MasterToken, respd.Data.SessionID) - if sessionParameters[clientRequestMfaToken] == true { - token := respd.Data.MfaToken -- setCredential(sc, mfaToken, token) -+ credentialsStorage.setCredential(sc, mfaToken, token) - } - if sessionParameters[clientStoreTemporaryCredential] == true { - token := respd.Data.IDToken -- setCredential(sc, idToken, token) -+ credentialsStorage.setCredential(sc, idToken, token) - } - return &respd.Data, nil - } -@@ -565,9 +565,9 @@ func authenticateWithConfig(sc *snowflakeConn) error { - } - - func fillCachedIDToken(sc *snowflakeConn) { -- getCredential(sc, idToken) -+ credentialsStorage.getCredential(sc, idToken) - } - - func fillCachedMfaToken(sc *snowflakeConn) { -- getCredential(sc, mfaToken) -+ credentialsStorage.getCredential(sc, mfaToken) - } -diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -index da8610e3..fc392662 100644 ---- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -+++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -@@ -4,6 +4,7 @@ package gosnowflake - - import ( - "encoding/json" -+ "fmt" - "os" - "path/filepath" - "runtime" -@@ -20,68 +21,231 @@ const ( - credCacheFileName = "temporary_credential.json" - ) - --var ( -- credCacheDir = "" -- credCache = "" -- localCredCache = map[string]string{} --) -+type secureStorageManager interface { -+ setCredential(sc *snowflakeConn, credType, token string) -+ getCredential(sc *snowflakeConn, credType string) -+ deleteCredential(sc *snowflakeConn, credType string) -+} - --var ( -- credCacheLock sync.RWMutex --) -+var credentialsStorage = newSecureStorageManager() -+ -+func newSecureStorageManager() secureStorageManager { -+ switch runtime.GOOS { -+ case "linux": -+ return newFileBasedSecureStorageManager() -+ case "darwin", "windows": -+ return newKeyringBasedSecureStorageManager() -+ default: -+ return newNoopSecureStorageManager() -+ } -+} -+ -+type fileBasedSecureStorageManager struct { -+ credCacheFilePath string -+ localCredCache map[string]string -+ credCacheLock sync.RWMutex -+} -+ -+func newFileBasedSecureStorageManager() secureStorageManager { -+ ssm := &fileBasedSecureStorageManager{ -+ localCredCache: map[string]string{}, -+ credCacheLock: sync.RWMutex{}, -+ } -+ credCacheDir := ssm.buildCredCacheDirPath() -+ if err := ssm.createCacheDir(credCacheDir); err != nil { -+ logger.Debugf("failed to create credentials cache dir. %v", err) -+ return newNoopSecureStorageManager() -+ } -+ credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) -+ logger.Infof("Credentials cache path: %v", credCacheFilePath) -+ ssm.credCacheFilePath = credCacheFilePath -+ return ssm -+} -+ -+func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { -+ _, err := os.Stat(credCacheDir) -+ if os.IsNotExist(err) { -+ if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { -+ return fmt.Errorf("failed to create cache directory. %v, err: %v", credCacheDir, err) -+ } -+ return nil -+ } -+ return err -+} -+ -+func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { -+ credCacheDir := os.Getenv(credCacheDirEnv) -+ if credCacheDir != "" { -+ return credCacheDir -+ } -+ home := os.Getenv("HOME") -+ if home == "" { -+ logger.Info("HOME is blank") -+ return "" -+ } -+ credCacheDir = filepath.Join(home, ".cache", "snowflake") -+ return credCacheDir -+} -+ -+func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { -+ if token == "" { -+ logger.Debug("no token provided") -+ } else { -+ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) -+ ssm.credCacheLock.Lock() -+ defer ssm.credCacheLock.Unlock() -+ ssm.localCredCache[credentialsKey] = token -+ -+ j, err := json.Marshal(ssm.localCredCache) -+ if err != nil { -+ logger.Warnf("failed to convert credential to JSON.") -+ return -+ } -+ -+ logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) -+ credCacheLockFileName := ssm.credCacheFilePath + ".lck" -+ logger.Debugf("Creating lock file. %v", credCacheLockFileName) -+ err = os.Mkdir(credCacheLockFileName, 0600) - --func createCredentialCacheDir() { -- credCacheDir = os.Getenv(credCacheDirEnv) -- if credCacheDir == "" { -- switch runtime.GOOS { -- case "windows": -- credCacheDir = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local", "Snowflake", "Caches") -- case "darwin": -- home := os.Getenv("HOME") -- if home == "" { -- logger.Info("HOME is blank.") -+ switch { -+ case os.IsExist(err): -+ statinfo, err := os.Stat(credCacheLockFileName) -+ if err != nil { -+ logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) -+ return -+ } -+ if time.Since(statinfo.ModTime()) < 15*time.Minute { -+ logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) -+ return -+ } -+ if err = os.Remove(credCacheLockFileName); err != nil { -+ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -+ return - } -- credCacheDir = filepath.Join(home, "Library", "Caches", "Snowflake") -- default: -- home := os.Getenv("HOME") -- if home == "" { -- logger.Info("HOME is blank") -+ if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { -+ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -+ return - } -- credCacheDir = filepath.Join(home, ".cache", "snowflake") -+ } -+ defer os.RemoveAll(credCacheLockFileName) -+ -+ if err = os.WriteFile(ssm.credCacheFilePath, j, 0644); err != nil { -+ logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) - } - } -+} - -- if _, err := os.Stat(credCacheDir); os.IsNotExist(err) { -- if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { -- logger.Debugf("Failed to create cache directory. %v, err: %v. ignored\n", credCacheDir, err) -+func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { -+ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) -+ ssm.credCacheLock.Lock() -+ defer ssm.credCacheLock.Unlock() -+ localCredCache := ssm.readTemporaryCacheFile() -+ cred := localCredCache[credentialsKey] -+ if cred != "" { -+ logger.Debug("Successfully read token. Returning as string") -+ } else { -+ logger.Debug("Returned credential is empty") -+ } -+ -+ if credType == idToken { -+ sc.cfg.IDToken = cred -+ } else if credType == mfaToken { -+ sc.cfg.MfaToken = cred -+ } else { -+ logger.Debugf("Unrecognized type %v for local cached credential", credType) -+ } -+} -+ -+func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { -+ jsonData, err := os.ReadFile(ssm.credCacheFilePath) -+ if err != nil { -+ logger.Debugf("Failed to read credential file: %v", err) -+ return nil -+ } -+ err = json.Unmarshal([]byte(jsonData), &ssm.localCredCache) -+ if err != nil { -+ logger.Debugf("failed to read JSON. Err: %v", err) -+ return nil -+ } -+ -+ return ssm.localCredCache -+} -+ -+func (ssm *fileBasedSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { -+ ssm.credCacheLock.Lock() -+ defer ssm.credCacheLock.Unlock() -+ credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) -+ delete(ssm.localCredCache, credentialsKey) -+ j, err := json.Marshal(ssm.localCredCache) -+ if err != nil { -+ logger.Warnf("failed to convert credential to JSON.") -+ return -+ } -+ ssm.writeTemporaryCacheFile(j) -+} -+ -+func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) { -+ logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) -+ credCacheLockFileName := ssm.credCacheFilePath + ".lck" -+ err := os.Mkdir(credCacheLockFileName, 0600) -+ logger.Debugf("Creating lock file. %v", credCacheLockFileName) -+ -+ switch { -+ case os.IsExist(err): -+ statinfo, err := os.Stat(credCacheLockFileName) -+ if err != nil { -+ logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) -+ return -+ } -+ if time.Since(statinfo.ModTime()) < 15*time.Minute { -+ logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) -+ return - } -+ if err = os.Remove(credCacheLockFileName); err != nil { -+ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -+ return -+ } -+ if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { -+ logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -+ return -+ } -+ } -+ defer os.RemoveAll(credCacheLockFileName) -+ -+ if err = os.WriteFile(ssm.credCacheFilePath, input, 0644); err != nil { -+ logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) - } -- credCache = filepath.Join(credCacheDir, credCacheFileName) -- logger.Infof("Cache directory: %v", credCache) - } - --func setCredential(sc *snowflakeConn, credType, token string) { -+type keyringSecureStorageManager struct { -+} -+ -+func newKeyringBasedSecureStorageManager() secureStorageManager { -+ return &keyringSecureStorageManager{} -+} -+ -+func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { - if token == "" { - logger.Debug("no token provided") - } else { -- var target string -+ var credentialsKey string - if runtime.GOOS == "windows" { -- target = driverName + ":" + credType -+ credentialsKey = driverName + ":" + credType - ring, _ := keyring.Open(keyring.Config{ - WinCredPrefix: strings.ToUpper(sc.cfg.Host), - ServiceName: strings.ToUpper(sc.cfg.User), - }) - item := keyring.Item{ -- Key: target, -+ Key: credentialsKey, - Data: []byte(token), - } - if err := ring.Set(item); err != nil { - logger.Debugf("Failed to write to Windows credential manager. Err: %v", err) - } - } else if runtime.GOOS == "darwin" { -- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) -+ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) - ring, _ := keyring.Open(keyring.Config{ -- ServiceName: target, -+ ServiceName: credentialsKey, - }) - account := strings.ToUpper(sc.cfg.User) - item := keyring.Item{ -@@ -91,33 +255,28 @@ func setCredential(sc *snowflakeConn, credType, token string) { - if err := ring.Set(item); err != nil { - logger.Debugf("Failed to write to keychain. Err: %v", err) - } -- } else if runtime.GOOS == "linux" { -- createCredentialCacheDir() -- writeTemporaryCredential(sc, credType, token) -- } else { -- logger.Debug("OS not supported for Local Secure Storage") - } - } - } - --func getCredential(sc *snowflakeConn, credType string) { -- var target string -+func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { -+ var credentialsKey string - cred := "" - if runtime.GOOS == "windows" { -- target = driverName + ":" + credType -+ credentialsKey = driverName + ":" + credType - ring, _ := keyring.Open(keyring.Config{ - WinCredPrefix: strings.ToUpper(sc.cfg.Host), - ServiceName: strings.ToUpper(sc.cfg.User), - }) -- i, err := ring.Get(target) -+ i, err := ring.Get(credentialsKey) - if err != nil { -- logger.Debugf("Failed to read target or could not find it in Windows Credential Manager. Error: %v", err) -+ logger.Debugf("Failed to read credentialsKey or could not find it in Windows Credential Manager. Error: %v", err) - } - cred = string(i.Data) - } else if runtime.GOOS == "darwin" { -- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) -+ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) - ring, _ := keyring.Open(keyring.Config{ -- ServiceName: target, -+ ServiceName: credentialsKey, - }) - account := strings.ToUpper(sc.cfg.User) - i, err := ring.Get(account) -@@ -130,11 +289,6 @@ func getCredential(sc *snowflakeConn, credType string) { - } else { - logger.Debug("Successfully read token. Returning as string") - } -- } else if runtime.GOOS == "linux" { -- createCredentialCacheDir() -- cred = readTemporaryCredential(sc, credType) -- } else { -- logger.Debug("OS not supported for Local Secure Storage") - } - - if credType == idToken { -@@ -146,139 +300,49 @@ func getCredential(sc *snowflakeConn, credType string) { - } - } - --func deleteCredential(sc *snowflakeConn, credType string) { -- target := driverName + ":" + credType -+func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { -+ credentialsKey := driverName + ":" + credType - if runtime.GOOS == "windows" { - ring, _ := keyring.Open(keyring.Config{ - WinCredPrefix: strings.ToUpper(sc.cfg.Host), - ServiceName: strings.ToUpper(sc.cfg.User), - }) -- err := ring.Remove(target) -+ err := ring.Remove(credentialsKey) - if err != nil { -- logger.Debugf("Failed to delete target in Windows Credential Manager. Error: %v", err) -+ logger.Debugf("Failed to delete credentialsKey in Windows Credential Manager. Error: %v", err) - } - } else if runtime.GOOS == "darwin" { -- target = convertTarget(sc.cfg.Host, sc.cfg.User, credType) -+ credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) - ring, _ := keyring.Open(keyring.Config{ -- ServiceName: target, -+ ServiceName: credentialsKey, - }) - account := strings.ToUpper(sc.cfg.User) - err := ring.Remove(account) - if err != nil { -- logger.Debugf("Failed to delete target in keychain. Error: %v", err) -+ logger.Debugf("Failed to delete credentialsKey in keychain. Error: %v", err) - } -- } else if runtime.GOOS == "linux" { -- deleteTemporaryCredential(sc, credType) - } - } - --// Reads temporary credential file when OS is Linux. --func readTemporaryCredential(sc *snowflakeConn, credType string) string { -- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) -- credCacheLock.Lock() -- defer credCacheLock.Unlock() -- localCredCache := readTemporaryCacheFile() -- cred := localCredCache[target] -- if cred != "" { -- logger.Debug("Successfully read token. Returning as string") -- } else { -- logger.Debug("Returned credential is empty") -- } -- return cred -+func buildCredentialsKey(host, user, credType string) string { -+ host = strings.ToUpper(host) -+ user = strings.ToUpper(user) -+ credType = strings.ToUpper(credType) -+ return host + ":" + user + ":" + driverName + ":" + credType - } - --// Writes to temporary credential file when OS is Linux. --func writeTemporaryCredential(sc *snowflakeConn, credType, token string) { -- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) -- credCacheLock.Lock() -- defer credCacheLock.Unlock() -- localCredCache[target] = token -- -- j, err := json.Marshal(localCredCache) -- if err != nil { -- logger.Warnf("failed to convert credential to JSON.") -- return -- } -- writeTemporaryCacheFile(j) -+type noopSecureStorageManager struct { - } - --func deleteTemporaryCredential(sc *snowflakeConn, credType string) { -- if credCacheDir == "" { -- logger.Debug("Cache file doesn't exist. Skipping deleting credential file.") -- } else { -- credCacheLock.Lock() -- defer credCacheLock.Unlock() -- target := convertTarget(sc.cfg.Host, sc.cfg.User, credType) -- delete(localCredCache, target) -- j, err := json.Marshal(localCredCache) -- if err != nil { -- logger.Warnf("failed to convert credential to JSON.") -- return -- } -- writeTemporaryCacheFile(j) -- } -+func newNoopSecureStorageManager() secureStorageManager { -+ return &noopSecureStorageManager{} - } - --func readTemporaryCacheFile() map[string]string { -- if credCache == "" { -- logger.Debug("Cache file doesn't exist. Skipping reading credential file.") -- return nil -- } -- jsonData, err := os.ReadFile(credCache) -- if err != nil { -- logger.Debugf("Failed to read credential file: %v", err) -- return nil -- } -- err = json.Unmarshal([]byte(jsonData), &localCredCache) -- if err != nil { -- logger.Debugf("failed to read JSON. Err: %v", err) -- return nil -- } -- -- return localCredCache -+func (ssm *noopSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { - } - --func writeTemporaryCacheFile(input []byte) { -- if credCache == "" { -- logger.Debug("Cache file doesn't exist. Skipping writing temporary credential file.") -- } else { -- logger.Debugf("writing credential cache file. %v\n", credCache) -- credCacheLockFileName := credCache + ".lck" -- err := os.Mkdir(credCacheLockFileName, 0600) -- logger.Debugf("Creating lock file. %v", credCacheLockFileName) -- -- switch { -- case os.IsExist(err): -- statinfo, err := os.Stat(credCacheLockFileName) -- if err != nil { -- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", credCache, err) -- return -- } -- if time.Since(statinfo.ModTime()) < 15*time.Minute { -- logger.Debugf("other process locks the cache file. %v. ignored.\n", credCache) -- return -- } -- if err = os.Remove(credCacheLockFileName); err != nil { -- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -- return -- } -- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { -- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -- return -- } -- } -- defer os.RemoveAll(credCacheLockFileName) -- -- if err = os.WriteFile(credCache, input, 0644); err != nil { -- logger.Debugf("Failed to write the cache file. File: %v err: %v.", credCache, err) -- } -- } -+func (ssm *noopSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { - } - --func convertTarget(host, user, credType string) string { -- host = strings.ToUpper(host) -- user = strings.ToUpper(user) -- credType = strings.ToUpper(credType) -- target := host + ":" + user + ":" + driverName + ":" + credType -- return target -+func (ssm *noopSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { //TODO implement me - } --- -2.34.1 - - -From 207a4e0397e893cc8d90e3ce551d36b11f1c537d Mon Sep 17 00:00:00 2001 -From: Kevin Lockwood -Date: Wed, 25 Jun 2025 16:39:09 -0700 -Subject: [PATCH 2/5] SNOW-1825790 Token cache refactor - v2 (#1299) - -Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/d8df82efb12b0428a88dfe4ec80e3b0936569d64.patch ---- - .../snowflakedb/gosnowflake/auth.go | 22 +-- - .../gosnowflake/secure_storage_manager.go | 146 ++++++++++-------- - 2 files changed, 88 insertions(+), 80 deletions(-) - -diff --git a/vendor/github.com/snowflakedb/gosnowflake/auth.go b/vendor/github.com/snowflakedb/gosnowflake/auth.go -index 888cd1ba..f2948475 100644 ---- a/vendor/github.com/snowflakedb/gosnowflake/auth.go -+++ b/vendor/github.com/snowflakedb/gosnowflake/auth.go -@@ -25,8 +25,6 @@ const ( - ) - - const ( -- idToken = "ID_TOKEN" -- mfaToken = "MFATOKEN" - clientStoreTemporaryCredential = "CLIENT_STORE_TEMPORARY_CREDENTIAL" - clientRequestMfaToken = "CLIENT_REQUEST_MFA_TOKEN" - idTokenAuthenticator = "ID_TOKEN" -@@ -364,10 +362,10 @@ func authenticate( - logger.WithContext(ctx).Errorln("Authentication FAILED") - sc.rest.TokenAccessor.SetTokens("", "", -1) - if sessionParameters[clientRequestMfaToken] == true { -- credentialsStorage.deleteCredential(sc, mfaToken) -+ credentialsStorage.deleteCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) - } - if sessionParameters[clientStoreTemporaryCredential] == true { -- credentialsStorage.deleteCredential(sc, idToken) -+ credentialsStorage.deleteCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) - } - code, err := strconv.Atoi(respd.Code) - if err != nil { -@@ -384,11 +382,11 @@ func authenticate( - sc.rest.TokenAccessor.SetTokens(respd.Data.Token, respd.Data.MasterToken, respd.Data.SessionID) - if sessionParameters[clientRequestMfaToken] == true { - token := respd.Data.MfaToken -- credentialsStorage.setCredential(sc, mfaToken, token) -+ credentialsStorage.setCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User), token) - } - if sessionParameters[clientStoreTemporaryCredential] == true { - token := respd.Data.IDToken -- credentialsStorage.setCredential(sc, idToken, token) -+ credentialsStorage.setCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User), token) - } - return &respd.Data, nil - } -@@ -513,7 +511,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { - sc.cfg.ClientStoreTemporaryCredential = ConfigBoolTrue - } - if sc.cfg.ClientStoreTemporaryCredential == ConfigBoolTrue { -- fillCachedIDToken(sc) -+ sc.cfg.IDToken = credentialsStorage.getCredential(newIDTokenSpec(sc.cfg.Host, sc.cfg.User)) - } - // Disable console login by default - if sc.cfg.DisableConsoleLogin == configBoolNotSet { -@@ -526,7 +524,7 @@ func authenticateWithConfig(sc *snowflakeConn) error { - sc.cfg.ClientRequestMfaToken = ConfigBoolTrue - } - if sc.cfg.ClientRequestMfaToken == ConfigBoolTrue { -- fillCachedMfaToken(sc) -+ sc.cfg.MfaToken = credentialsStorage.getCredential(newMfaTokenSpec(sc.cfg.Host, sc.cfg.User)) - } - } - -@@ -563,11 +561,3 @@ func authenticateWithConfig(sc *snowflakeConn) error { - sc.ctx = context.WithValue(sc.ctx, SFSessionIDKey, authData.SessionID) - return nil - } -- --func fillCachedIDToken(sc *snowflakeConn) { -- credentialsStorage.getCredential(sc, idToken) --} -- --func fillCachedMfaToken(sc *snowflakeConn) { -- credentialsStorage.getCredential(sc, mfaToken) --} -diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -index fc392662..6be71a57 100644 ---- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -+++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -@@ -15,16 +15,48 @@ import ( - "github.com/99designs/keyring" - ) - -+type tokenType string -+ -+const ( -+ idToken tokenType = "ID_TOKEN" -+ mfaToken tokenType = "MFATOKEN" -+) -+ - const ( - driverName = "SNOWFLAKE-GO-DRIVER" - credCacheDirEnv = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR" - credCacheFileName = "temporary_credential.json" - ) - -+type secureTokenSpec struct { -+ host, user string -+ tokenType tokenType -+} -+ -+func (t *secureTokenSpec) buildKey() string { -+ return buildCredentialsKey(t.host, t.user, t.tokenType) -+} -+ -+func newMfaTokenSpec(host, user string) *secureTokenSpec { -+ return &secureTokenSpec{ -+ host, -+ user, -+ mfaToken, -+ } -+} -+ -+func newIDTokenSpec(host, user string) *secureTokenSpec { -+ return &secureTokenSpec{ -+ host, -+ user, -+ idToken, -+ } -+} -+ - type secureStorageManager interface { -- setCredential(sc *snowflakeConn, credType, token string) -- getCredential(sc *snowflakeConn, credType string) -- deleteCredential(sc *snowflakeConn, credType string) -+ setCredential(tokenSpec *secureTokenSpec, value string) -+ getCredential(tokenSpec *secureTokenSpec) string -+ deleteCredential(tokenSpec *secureTokenSpec) - } - - var credentialsStorage = newSecureStorageManager() -@@ -32,7 +64,12 @@ var credentialsStorage = newSecureStorageManager() - func newSecureStorageManager() secureStorageManager { - switch runtime.GOOS { - case "linux": -- return newFileBasedSecureStorageManager() -+ ssm, err := newFileBasedSecureStorageManager() -+ if err != nil { -+ logger.Debugf("failed to create credentials cache dir. %v", err) -+ return newNoopSecureStorageManager() -+ } -+ return ssm - case "darwin", "windows": - return newKeyringBasedSecureStorageManager() - default: -@@ -46,20 +83,19 @@ type fileBasedSecureStorageManager struct { - credCacheLock sync.RWMutex - } - --func newFileBasedSecureStorageManager() secureStorageManager { -+func newFileBasedSecureStorageManager() (*fileBasedSecureStorageManager, error) { - ssm := &fileBasedSecureStorageManager{ - localCredCache: map[string]string{}, - credCacheLock: sync.RWMutex{}, - } - credCacheDir := ssm.buildCredCacheDirPath() - if err := ssm.createCacheDir(credCacheDir); err != nil { -- logger.Debugf("failed to create credentials cache dir. %v", err) -- return newNoopSecureStorageManager() -+ return nil, err - } - credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) - logger.Infof("Credentials cache path: %v", credCacheFilePath) - ssm.credCacheFilePath = credCacheFilePath -- return ssm -+ return ssm, nil - } - - func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { -@@ -87,14 +123,14 @@ func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { - return credCacheDir - } - --func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { -- if token == "" { -+func (ssm *fileBasedSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { -+ if value == "" { - logger.Debug("no token provided") - } else { -- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) -+ credentialsKey := tokenSpec.buildKey() - ssm.credCacheLock.Lock() - defer ssm.credCacheLock.Unlock() -- ssm.localCredCache[credentialsKey] = token -+ ssm.localCredCache[credentialsKey] = value - - j, err := json.Marshal(ssm.localCredCache) - if err != nil { -@@ -135,8 +171,8 @@ func (ssm *fileBasedSecureStorageManager) setCredential(sc *snowflakeConn, credT - } - } - --func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { -- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) -+func (ssm *fileBasedSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { -+ credentialsKey := tokenSpec.buildKey() - ssm.credCacheLock.Lock() - defer ssm.credCacheLock.Unlock() - localCredCache := ssm.readTemporaryCacheFile() -@@ -146,14 +182,7 @@ func (ssm *fileBasedSecureStorageManager) getCredential(sc *snowflakeConn, credT - } else { - logger.Debug("Returned credential is empty") - } -- -- if credType == idToken { -- sc.cfg.IDToken = cred -- } else if credType == mfaToken { -- sc.cfg.MfaToken = cred -- } else { -- logger.Debugf("Unrecognized type %v for local cached credential", credType) -- } -+ return cred - } - - func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { -@@ -171,10 +200,10 @@ func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]st - return ssm.localCredCache - } - --func (ssm *fileBasedSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { -+func (ssm *fileBasedSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { - ssm.credCacheLock.Lock() - defer ssm.credCacheLock.Unlock() -- credentialsKey := buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) -+ credentialsKey := tokenSpec.buildKey() - delete(ssm.localCredCache, credentialsKey) - j, err := json.Marshal(ssm.localCredCache) - if err != nil { -@@ -220,37 +249,35 @@ func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) - type keyringSecureStorageManager struct { - } - --func newKeyringBasedSecureStorageManager() secureStorageManager { -+func newKeyringBasedSecureStorageManager() *keyringSecureStorageManager { - return &keyringSecureStorageManager{} - } - --func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { -- if token == "" { -+func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { -+ if value == "" { - logger.Debug("no token provided") - } else { -- var credentialsKey string -+ credentialsKey := tokenSpec.buildKey() - if runtime.GOOS == "windows" { -- credentialsKey = driverName + ":" + credType - ring, _ := keyring.Open(keyring.Config{ -- WinCredPrefix: strings.ToUpper(sc.cfg.Host), -- ServiceName: strings.ToUpper(sc.cfg.User), -+ WinCredPrefix: strings.ToUpper(tokenSpec.host), -+ ServiceName: strings.ToUpper(tokenSpec.user), - }) - item := keyring.Item{ - Key: credentialsKey, -- Data: []byte(token), -+ Data: []byte(value), - } - if err := ring.Set(item); err != nil { - logger.Debugf("Failed to write to Windows credential manager. Err: %v", err) - } - } else if runtime.GOOS == "darwin" { -- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) - ring, _ := keyring.Open(keyring.Config{ - ServiceName: credentialsKey, - }) -- account := strings.ToUpper(sc.cfg.User) -+ account := strings.ToUpper(tokenSpec.user) - item := keyring.Item{ - Key: account, -- Data: []byte(token), -+ Data: []byte(value), - } - if err := ring.Set(item); err != nil { - logger.Debugf("Failed to write to keychain. Err: %v", err) -@@ -259,14 +286,13 @@ func (ssm *keyringSecureStorageManager) setCredential(sc *snowflakeConn, credTyp - } - } - --func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { -- var credentialsKey string -+func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { - cred := "" -+ credentialsKey := tokenSpec.buildKey() - if runtime.GOOS == "windows" { -- credentialsKey = driverName + ":" + credType - ring, _ := keyring.Open(keyring.Config{ -- WinCredPrefix: strings.ToUpper(sc.cfg.Host), -- ServiceName: strings.ToUpper(sc.cfg.User), -+ WinCredPrefix: strings.ToUpper(tokenSpec.host), -+ ServiceName: strings.ToUpper(tokenSpec.user), - }) - i, err := ring.Get(credentialsKey) - if err != nil { -@@ -274,11 +300,10 @@ func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credTyp - } - cred = string(i.Data) - } else if runtime.GOOS == "darwin" { -- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) - ring, _ := keyring.Open(keyring.Config{ - ServiceName: credentialsKey, - }) -- account := strings.ToUpper(sc.cfg.User) -+ account := strings.ToUpper(tokenSpec.user) - i, err := ring.Get(account) - if err != nil { - logger.Debugf("Failed to find the item in keychain or item does not exist. Error: %v", err) -@@ -290,33 +315,25 @@ func (ssm *keyringSecureStorageManager) getCredential(sc *snowflakeConn, credTyp - logger.Debug("Successfully read token. Returning as string") - } - } -- -- if credType == idToken { -- sc.cfg.IDToken = cred -- } else if credType == mfaToken { -- sc.cfg.MfaToken = cred -- } else { -- logger.Debugf("Unrecognized type %v for local cached credential", credType) -- } -+ return cred - } - --func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { -- credentialsKey := driverName + ":" + credType -+func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { -+ credentialsKey := tokenSpec.buildKey() - if runtime.GOOS == "windows" { - ring, _ := keyring.Open(keyring.Config{ -- WinCredPrefix: strings.ToUpper(sc.cfg.Host), -- ServiceName: strings.ToUpper(sc.cfg.User), -+ WinCredPrefix: strings.ToUpper(tokenSpec.host), -+ ServiceName: strings.ToUpper(tokenSpec.user), - }) -- err := ring.Remove(credentialsKey) -+ err := ring.Remove(string(credentialsKey)) - if err != nil { - logger.Debugf("Failed to delete credentialsKey in Windows Credential Manager. Error: %v", err) - } - } else if runtime.GOOS == "darwin" { -- credentialsKey = buildCredentialsKey(sc.cfg.Host, sc.cfg.User, credType) - ring, _ := keyring.Open(keyring.Config{ - ServiceName: credentialsKey, - }) -- account := strings.ToUpper(sc.cfg.User) -+ account := strings.ToUpper(tokenSpec.user) - err := ring.Remove(account) - if err != nil { - logger.Debugf("Failed to delete credentialsKey in keychain. Error: %v", err) -@@ -324,25 +341,26 @@ func (ssm *keyringSecureStorageManager) deleteCredential(sc *snowflakeConn, cred - } - } - --func buildCredentialsKey(host, user, credType string) string { -+func buildCredentialsKey(host, user string, credType tokenType) string { - host = strings.ToUpper(host) - user = strings.ToUpper(user) -- credType = strings.ToUpper(credType) -- return host + ":" + user + ":" + driverName + ":" + credType -+ credTypeStr := strings.ToUpper(string(credType)) -+ return host + ":" + user + ":" + driverName + ":" + credTypeStr - } - - type noopSecureStorageManager struct { - } - --func newNoopSecureStorageManager() secureStorageManager { -+func newNoopSecureStorageManager() *noopSecureStorageManager { - return &noopSecureStorageManager{} - } - --func (ssm *noopSecureStorageManager) setCredential(sc *snowflakeConn, credType, token string) { -+func (ssm *noopSecureStorageManager) setCredential(_ *secureTokenSpec, _ string) { - } - --func (ssm *noopSecureStorageManager) getCredential(sc *snowflakeConn, credType string) { -+func (ssm *noopSecureStorageManager) getCredential(_ *secureTokenSpec) string { -+ return "" - } - --func (ssm *noopSecureStorageManager) deleteCredential(sc *snowflakeConn, credType string) { //TODO implement me -+func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { //TODO implement me - } --- -2.34.1 - - -From 77a81aa504d9729edc50fab0061ef36dda7dfc82 Mon Sep 17 00:00:00 2001 -From: Kevin Lockwood -Date: Wed, 25 Jun 2025 16:40:59 -0700 -Subject: [PATCH 3/5] SNOW-1825790 Implement safer file based token cache - (#1327) - -Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/61b822d82822f9672b6749345bf64a044a0532bf.patch ---- - .../gosnowflake/os_specific_posix.go | 25 + - .../gosnowflake/os_specific_windows.go | 12 + - .../gosnowflake/secure_storage_manager.go | 463 +++++++++++++----- - 3 files changed, 370 insertions(+), 130 deletions(-) - create mode 100644 vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go - create mode 100644 vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go - -diff --git a/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go b/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go -new file mode 100644 -index 00000000..2d01f5c0 ---- /dev/null -+++ b/vendor/github.com/snowflakedb/gosnowflake/os_specific_posix.go -@@ -0,0 +1,25 @@ -+//go:build darwin || linux -+ -+package gosnowflake -+ -+import ( -+ "fmt" -+ "os" -+ "syscall" -+) -+ -+func provideFileOwner(file *os.File) (uint32, error) { -+ info, err := file.Stat() -+ if err != nil { -+ return 0, err -+ } -+ return provideOwnerFromStat(info, file.Name()) -+} -+ -+func provideOwnerFromStat(info os.FileInfo, filepath string) (uint32, error) { -+ nativeStat, ok := info.Sys().(*syscall.Stat_t) -+ if !ok { -+ return 0, fmt.Errorf("cannot cast file info for %v to *syscall.Stat_t", filepath) -+ } -+ return nativeStat.Uid, nil -+} -diff --git a/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go b/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go -new file mode 100644 -index 00000000..293123e0 ---- /dev/null -+++ b/vendor/github.com/snowflakedb/gosnowflake/os_specific_windows.go -@@ -0,0 +1,12 @@ -+// go:build windows -+ -+package gosnowflake -+ -+import ( -+ "errors" -+ "os" -+) -+ -+func provideFileOwner(file *os.File) (uint32, error) { -+ return 0, errors.New("provideFileOwner is unsupported on windows") -+} -diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -index 6be71a57..ab33874e 100644 ---- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -+++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -@@ -3,16 +3,21 @@ - package gosnowflake - - import ( -+ "crypto/sha256" -+ "encoding/hex" - "encoding/json" -+ "errors" - "fmt" -+ "github.com/99designs/keyring" -+ "io" - "os" -+ "os/user" - "path/filepath" - "runtime" -+ "strconv" - "strings" - "sync" - "time" -- -- "github.com/99designs/keyring" - ) - - type tokenType string -@@ -23,17 +28,27 @@ const ( - ) - - const ( -- driverName = "SNOWFLAKE-GO-DRIVER" - credCacheDirEnv = "SF_TEMPORARY_CREDENTIAL_CACHE_DIR" -- credCacheFileName = "temporary_credential.json" -+ credCacheFileName = "credential_cache_v1.json" - ) - -+type cacheDirConf struct { -+ envVar string -+ pathSegments []string -+} -+ -+var defaultLinuxCacheDirConf = []cacheDirConf{ -+ {envVar: credCacheDirEnv, pathSegments: []string{}}, -+ {envVar: "XDG_CACHE_DIR", pathSegments: []string{"snowflake"}}, -+ {envVar: "HOME", pathSegments: []string{".cache", "snowflake"}}, -+} -+ - type secureTokenSpec struct { - host, user string - tokenType tokenType - } - --func (t *secureTokenSpec) buildKey() string { -+func (t *secureTokenSpec) buildKey() (string, error) { - return buildCredentialsKey(t.host, t.user, t.tokenType) - } - -@@ -69,181 +84,328 @@ func newSecureStorageManager() secureStorageManager { - logger.Debugf("failed to create credentials cache dir. %v", err) - return newNoopSecureStorageManager() - } -- return ssm -+ return &threadSafeSecureStorageManager{&sync.Mutex{}, ssm} - case "darwin", "windows": -- return newKeyringBasedSecureStorageManager() -+ return &threadSafeSecureStorageManager{&sync.Mutex{}, newKeyringBasedSecureStorageManager()} - default: -+ logger.Warnf("OS %v does not support credentials cache", runtime.GOOS) - return newNoopSecureStorageManager() - } - } - - type fileBasedSecureStorageManager struct { -- credCacheFilePath string -- localCredCache map[string]string -- credCacheLock sync.RWMutex -+ credDirPath string - } - - func newFileBasedSecureStorageManager() (*fileBasedSecureStorageManager, error) { -- ssm := &fileBasedSecureStorageManager{ -- localCredCache: map[string]string{}, -- credCacheLock: sync.RWMutex{}, -- } -- credCacheDir := ssm.buildCredCacheDirPath() -- if err := ssm.createCacheDir(credCacheDir); err != nil { -+ credDirPath, err := buildCredCacheDirPath(defaultLinuxCacheDirConf) -+ if err != nil { - return nil, err - } -- credCacheFilePath := filepath.Join(credCacheDir, credCacheFileName) -- logger.Infof("Credentials cache path: %v", credCacheFilePath) -- ssm.credCacheFilePath = credCacheFilePath -+ ssm := &fileBasedSecureStorageManager{ -+ credDirPath: credDirPath, -+ } - return ssm, nil - } - --func (ssm *fileBasedSecureStorageManager) createCacheDir(credCacheDir string) error { -- _, err := os.Stat(credCacheDir) -- if os.IsNotExist(err) { -- if err = os.MkdirAll(credCacheDir, os.ModePerm); err != nil { -- return fmt.Errorf("failed to create cache directory. %v, err: %v", credCacheDir, err) -+func lookupCacheDir(envVar string, pathSegments ...string) (string, error) { -+ envVal := os.Getenv(envVar) -+ if envVal == "" { -+ return "", fmt.Errorf("environment variable %s not set", envVar) -+ } -+ -+ fileInfo, err := os.Stat(envVal) -+ if err != nil { -+ return "", fmt.Errorf("failed to stat %s=%s, due to %v", envVar, envVal, err) -+ } -+ -+ if !fileInfo.IsDir() { -+ return "", fmt.Errorf("environment variable %s=%s is not a directory", envVar, envVal) -+ } -+ -+ cacheDir := filepath.Join(envVal, filepath.Join(pathSegments...)) -+ parentOfCacheDir := cacheDir[:strings.LastIndex(cacheDir, "/")] -+ -+ if err = os.MkdirAll(parentOfCacheDir, os.FileMode(0755)); err != nil { -+ return "", err -+ } -+ -+ // We don't check if permissions are incorrect here if a directory exists, because we check it later. -+ if err = os.Mkdir(cacheDir, os.FileMode(0700)); err != nil && !errors.Is(err, os.ErrExist) { -+ return "", err -+ } -+ -+ return cacheDir, nil -+} -+ -+func buildCredCacheDirPath(confs []cacheDirConf) (string, error) { -+ for _, conf := range confs { -+ path, err := lookupCacheDir(conf.envVar, conf.pathSegments...) -+ if err != nil { -+ logger.Debugf("Skipping %s in cache directory lookup due to %v", conf.envVar, err) -+ } else { -+ logger.Debugf("Using %s as cache directory", path) -+ return path, nil - } -- return nil - } -- return err -+ -+ return "", errors.New("no credentials cache directory found") - } - --func (ssm *fileBasedSecureStorageManager) buildCredCacheDirPath() string { -- credCacheDir := os.Getenv(credCacheDirEnv) -- if credCacheDir != "" { -- return credCacheDir -+func (ssm *fileBasedSecureStorageManager) getTokens(data map[string]any) map[string]interface{} { -+ val, ok := data["tokens"] -+ if !ok { -+ return map[string]interface{}{} - } -- home := os.Getenv("HOME") -- if home == "" { -- logger.Info("HOME is blank") -- return "" -+ -+ tokens, ok := val.(map[string]interface{}) -+ if !ok { -+ return map[string]interface{}{} - } -- credCacheDir = filepath.Join(home, ".cache", "snowflake") -- return credCacheDir -+ -+ return tokens -+} -+ -+func (ssm *fileBasedSecureStorageManager) withLock(action func(cacheFile *os.File)) { -+ err := ssm.lockFile() -+ if err != nil { -+ logger.Warnf("Unable to lock cache. %v", err) -+ return -+ } -+ defer ssm.unlockFile() -+ -+ ssm.withCacheFile(action) -+} -+ -+func (ssm *fileBasedSecureStorageManager) withCacheFile(action func(*os.File)) { -+ cacheFile, err := os.OpenFile(ssm.credFilePath(), os.O_CREATE|os.O_RDWR, 0600) -+ if err != nil { -+ logger.Warnf("cannot access %v. %v", ssm.credFilePath(), err) -+ return -+ } -+ defer func(file *os.File) { -+ if err := file.Close(); err != nil { -+ logger.Warnf("cannot release file descriptor for %v. %v", ssm.credFilePath(), err) -+ } -+ }(cacheFile) -+ -+ cacheDir, err := os.Open(ssm.credDirPath) -+ if err != nil { -+ logger.Warnf("cannot access %v. %v", ssm.credDirPath, err) -+ } -+ defer func(file *os.File) { -+ if err := file.Close(); err != nil { -+ logger.Warnf("cannot release file descriptor for %v. %v", cacheDir, err) -+ } -+ }(cacheDir) -+ -+ if err := ssm.ensurePermissionsAndOwner(cacheFile, 0600); err != nil { -+ logger.Warnf("failed to ensure permission for temporary cache file. %v", err) -+ return -+ } -+ if err := ssm.ensurePermissionsAndOwner(cacheDir, 0700|os.ModeDir); err != nil { -+ logger.Warnf("failed to ensure permission for temporary cache dir. %v", err) -+ return -+ } -+ -+ action(cacheFile) - } - - func (ssm *fileBasedSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { -- if value == "" { -- logger.Debug("no token provided") -- } else { -- credentialsKey := tokenSpec.buildKey() -- ssm.credCacheLock.Lock() -- defer ssm.credCacheLock.Unlock() -- ssm.localCredCache[credentialsKey] = value -+ credentialsKey, err := tokenSpec.buildKey() -+ if err != nil { -+ logger.Warn(err) -+ return -+ } - -- j, err := json.Marshal(ssm.localCredCache) -+ ssm.withLock(func(cacheFile *os.File) { -+ credCache, err := ssm.readTemporaryCacheFile(cacheFile) - if err != nil { -- logger.Warnf("failed to convert credential to JSON.") -+ logger.Warnf("Error while reading cache file. %v", err) - return - } -+ tokens := ssm.getTokens(credCache) -+ tokens[credentialsKey] = value -+ credCache["tokens"] = tokens -+ err = ssm.writeTemporaryCacheFile(credCache, cacheFile) -+ if err != nil { -+ logger.Warnf("Set credential failed. Unable to write cache. %v", err) -+ } -+ }) -+} - -- logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) -- credCacheLockFileName := ssm.credCacheFilePath + ".lck" -- logger.Debugf("Creating lock file. %v", credCacheLockFileName) -- err = os.Mkdir(credCacheLockFileName, 0600) -- -- switch { -- case os.IsExist(err): -- statinfo, err := os.Stat(credCacheLockFileName) -- if err != nil { -- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) -- return -- } -- if time.Since(statinfo.ModTime()) < 15*time.Minute { -- logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) -- return -- } -- if err = os.Remove(credCacheLockFileName); err != nil { -- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -- return -- } -- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { -- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -- return -- } -+func (ssm *fileBasedSecureStorageManager) lockPath() string { -+ return filepath.Join(ssm.credDirPath, credCacheFileName+".lck") -+} -+ -+func (ssm *fileBasedSecureStorageManager) lockFile() error { -+ const numRetries = 10 -+ const retryInterval = 100 * time.Millisecond -+ lockPath := ssm.lockPath() -+ -+ fileInfo, err := os.Stat(lockPath) -+ if err != nil && !errors.Is(err, os.ErrNotExist) { -+ return fmt.Errorf("failed to stat %v and determine if lock is stale. err: %v", lockPath, err) -+ } -+ -+ // removing stale lock -+ now := time.Now() -+ if !errors.Is(err, os.ErrNotExist) && fileInfo.ModTime().Add(time.Second).UnixNano() < now.UnixNano() { -+ logger.Debugf("removing credentials cache lock file, stale for %vms", (now.UnixNano()-fileInfo.ModTime().UnixNano())/1000/1000) -+ err = os.Remove(lockPath) -+ if err != nil { -+ return fmt.Errorf("failed to remove %v while trying to remove stale lock. err: %v", lockPath, err) - } -- defer os.RemoveAll(credCacheLockFileName) -+ } - -- if err = os.WriteFile(ssm.credCacheFilePath, j, 0644); err != nil { -- logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) -+ locked := false -+ for i := 0; i < numRetries; i++ { -+ err := os.Mkdir(lockPath, 0700) -+ if err != nil { -+ if errors.Is(err, os.ErrExist) { -+ time.Sleep(retryInterval) -+ continue -+ } -+ return fmt.Errorf("failed to create cache lock: %v, err: %v", lockPath, err) - } -+ locked = true -+ break -+ } -+ if !locked { -+ return fmt.Errorf("failed to lock cache. lockPath: %v", lockPath) -+ } -+ return nil -+} -+ -+func (ssm *fileBasedSecureStorageManager) unlockFile() { -+ lockPath := ssm.lockPath() -+ err := os.Remove(lockPath) -+ if err != nil { -+ logger.Warnf("Failed to unlock cache lock: %v. %v", lockPath, err) - } - } - - func (ssm *fileBasedSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { -- credentialsKey := tokenSpec.buildKey() -- ssm.credCacheLock.Lock() -- defer ssm.credCacheLock.Unlock() -- localCredCache := ssm.readTemporaryCacheFile() -- cred := localCredCache[credentialsKey] -- if cred != "" { -- logger.Debug("Successfully read token. Returning as string") -- } else { -- logger.Debug("Returned credential is empty") -+ credentialsKey, err := tokenSpec.buildKey() -+ if err != nil { -+ logger.Warn(err) -+ return "" - } -- return cred -+ -+ ret := "" -+ ssm.withLock(func(cacheFile *os.File) { -+ credCache, err := ssm.readTemporaryCacheFile(cacheFile) -+ if err != nil { -+ logger.Warnf("Error while reading cache file. %v", err) -+ return -+ } -+ cred, ok := ssm.getTokens(credCache)[credentialsKey] -+ if !ok { -+ return -+ } -+ -+ credStr, ok := cred.(string) -+ if !ok { -+ return -+ } -+ -+ ret = credStr -+ }) -+ return ret - } - --func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile() map[string]string { -- jsonData, err := os.ReadFile(ssm.credCacheFilePath) -+func (ssm *fileBasedSecureStorageManager) credFilePath() string { -+ return filepath.Join(ssm.credDirPath, credCacheFileName) -+} -+ -+func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, expectedMode os.FileMode) error { -+ fileInfo, err := f.Stat() - if err != nil { -- logger.Debugf("Failed to read credential file: %v", err) -- return nil -+ return err -+ } -+ -+ if fileInfo.Mode().Perm() != expectedMode&os.ModePerm { -+ return fmt.Errorf("incorrect permissions(%v, expected %v) for credential file", fileInfo.Mode(), expectedMode) -+ } -+ -+ ownerUID, err := provideFileOwner(f) -+ if err != nil && !errors.Is(err, os.ErrNotExist) { -+ return err - } -- err = json.Unmarshal([]byte(jsonData), &ssm.localCredCache) -+ currentUser, err := user.Current() - if err != nil { -- logger.Debugf("failed to read JSON. Err: %v", err) -+ return err -+ } -+ if errors.Is(err, os.ErrNotExist) { - return nil - } -+ if strconv.Itoa(int(ownerUID)) != currentUser.Uid { -+ return errors.New("incorrect owner of " + ssm.credDirPath) -+ } -+ return nil -+} -+ -+func (ssm *fileBasedSecureStorageManager) readTemporaryCacheFile(cacheFile *os.File) (map[string]any, error) { -+ -+ jsonData, err := io.ReadAll(cacheFile) -+ if err != nil { -+ logger.Warnf("Failed to read credential cache file. %v.\n", err) -+ return map[string]any{}, nil -+ } -+ if _, err = cacheFile.Seek(0, 0); err != nil { -+ return map[string]any{}, fmt.Errorf("cannot seek to the beginning of a cache file. %v", err) -+ } -+ -+ if len(jsonData) == 0 { -+ // Happens when the file didn't exist before. -+ return map[string]any{}, nil -+ } - -- return ssm.localCredCache -+ credentialsMap := map[string]any{} -+ err = json.Unmarshal(jsonData, &credentialsMap) -+ if err != nil { -+ return map[string]any{}, fmt.Errorf("failed to unmarshal credential cache file. %v", err) -+ } -+ -+ return credentialsMap, nil - } - - func (ssm *fileBasedSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { -- ssm.credCacheLock.Lock() -- defer ssm.credCacheLock.Unlock() -- credentialsKey := tokenSpec.buildKey() -- delete(ssm.localCredCache, credentialsKey) -- j, err := json.Marshal(ssm.localCredCache) -+ credentialsKey, err := tokenSpec.buildKey() - if err != nil { -- logger.Warnf("failed to convert credential to JSON.") -+ logger.Warn(err) - return - } -- ssm.writeTemporaryCacheFile(j) --} - --func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(input []byte) { -- logger.Debugf("writing credential cache file. %v\n", ssm.credCacheFilePath) -- credCacheLockFileName := ssm.credCacheFilePath + ".lck" -- err := os.Mkdir(credCacheLockFileName, 0600) -- logger.Debugf("Creating lock file. %v", credCacheLockFileName) -- -- switch { -- case os.IsExist(err): -- statinfo, err := os.Stat(credCacheLockFileName) -+ ssm.withLock(func(cacheFile *os.File) { -+ credCache, err := ssm.readTemporaryCacheFile(cacheFile) - if err != nil { -- logger.Debugf("failed to write credential cache file. file: %v, err: %v. ignored.\n", ssm.credCacheFilePath, err) -- return -- } -- if time.Since(statinfo.ModTime()) < 15*time.Minute { -- logger.Debugf("other process locks the cache file. %v. ignored.\n", ssm.credCacheFilePath) -- return -- } -- if err = os.Remove(credCacheLockFileName); err != nil { -- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -+ logger.Warnf("Error while reading cache file. %v", err) - return - } -- if err = os.Mkdir(credCacheLockFileName, 0600); err != nil { -- logger.Debugf("failed to delete lock file. file: %v, err: %v. ignored.\n", credCacheLockFileName, err) -- return -+ delete(ssm.getTokens(credCache), credentialsKey) -+ -+ err = ssm.writeTemporaryCacheFile(credCache, cacheFile) -+ if err != nil { -+ logger.Warnf("Set credential failed. Unable to write cache. %v", err) - } -+ }) -+} -+ -+func (ssm *fileBasedSecureStorageManager) writeTemporaryCacheFile(cache map[string]any, cacheFile *os.File) error { -+ bytes, err := json.Marshal(cache) -+ if err != nil { -+ return fmt.Errorf("failed to marshal credential cache map. %w", err) - } -- defer os.RemoveAll(credCacheLockFileName) - -- if err = os.WriteFile(ssm.credCacheFilePath, input, 0644); err != nil { -- logger.Debugf("Failed to write the cache file. File: %v err: %v.", ssm.credCacheFilePath, err) -+ if err = cacheFile.Truncate(0); err != nil { -+ return fmt.Errorf("error while truncating credentials cache. %v", err) -+ } -+ _, err = cacheFile.Write(bytes) -+ if err != nil { -+ return fmt.Errorf("failed to write the credential cache file: %w", err) - } -+ return nil - } - - type keyringSecureStorageManager struct { -@@ -257,7 +419,11 @@ func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec - if value == "" { - logger.Debug("no token provided") - } else { -- credentialsKey := tokenSpec.buildKey() -+ credentialsKey, err := tokenSpec.buildKey() -+ if err != nil { -+ logger.Warn(err) -+ return -+ } - if runtime.GOOS == "windows" { - ring, _ := keyring.Open(keyring.Config{ - WinCredPrefix: strings.ToUpper(tokenSpec.host), -@@ -288,7 +454,11 @@ func (ssm *keyringSecureStorageManager) setCredential(tokenSpec *secureTokenSpec - - func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { - cred := "" -- credentialsKey := tokenSpec.buildKey() -+ credentialsKey, err := tokenSpec.buildKey() -+ if err != nil { -+ logger.Warn(err) -+ return "" -+ } - if runtime.GOOS == "windows" { - ring, _ := keyring.Open(keyring.Config{ - WinCredPrefix: strings.ToUpper(tokenSpec.host), -@@ -319,7 +489,11 @@ func (ssm *keyringSecureStorageManager) getCredential(tokenSpec *secureTokenSpec - } - - func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { -- credentialsKey := tokenSpec.buildKey() -+ credentialsKey, err := tokenSpec.buildKey() -+ if err != nil { -+ logger.Warn(err) -+ return -+ } - if runtime.GOOS == "windows" { - ring, _ := keyring.Open(keyring.Config{ - WinCredPrefix: strings.ToUpper(tokenSpec.host), -@@ -341,11 +515,17 @@ func (ssm *keyringSecureStorageManager) deleteCredential(tokenSpec *secureTokenS - } - } - --func buildCredentialsKey(host, user string, credType tokenType) string { -- host = strings.ToUpper(host) -- user = strings.ToUpper(user) -- credTypeStr := strings.ToUpper(string(credType)) -- return host + ":" + user + ":" + driverName + ":" + credTypeStr -+func buildCredentialsKey(host, user string, credType tokenType) (string, error) { -+ if host == "" { -+ return "", errors.New("host is not provided to store in token cache, skipping") -+ } -+ if user == "" { -+ return "", errors.New("user is not provided to store in token cache, skipping") -+ } -+ plainCredKey := host + ":" + user + ":" + string(credType) -+ checksum := sha256.New() -+ checksum.Write([]byte(plainCredKey)) -+ return hex.EncodeToString(checksum.Sum(nil)), nil - } - - type noopSecureStorageManager struct { -@@ -362,5 +542,28 @@ func (ssm *noopSecureStorageManager) getCredential(_ *secureTokenSpec) string { - return "" - } - --func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { //TODO implement me -+func (ssm *noopSecureStorageManager) deleteCredential(_ *secureTokenSpec) { -+} -+ -+type threadSafeSecureStorageManager struct { -+ mu *sync.Mutex -+ delegate secureStorageManager -+} -+ -+func (ssm *threadSafeSecureStorageManager) setCredential(tokenSpec *secureTokenSpec, value string) { -+ ssm.mu.Lock() -+ defer ssm.mu.Unlock() -+ ssm.delegate.setCredential(tokenSpec, value) -+} -+ -+func (ssm *threadSafeSecureStorageManager) getCredential(tokenSpec *secureTokenSpec) string { -+ ssm.mu.Lock() -+ defer ssm.mu.Unlock() -+ return ssm.delegate.getCredential(tokenSpec) -+} -+ -+func (ssm *threadSafeSecureStorageManager) deleteCredential(tokenSpec *secureTokenSpec) { -+ ssm.mu.Lock() -+ defer ssm.mu.Unlock() -+ ssm.delegate.deleteCredential(tokenSpec) - } --- -2.34.1 - - -From e8e6d49a988ac24c0691434b3961911f091e0a90 Mon Sep 17 00:00:00 2001 -From: Piotr Fus -Date: Tue, 1 Apr 2025 10:55:30 +0200 -Subject: [PATCH 4/5] =?UTF-8?q?SNOW-1825790=20Fix=20error=20message=20when?= - =?UTF-8?q?=20credentials=20file=20owner=20does=20not=20m=E2=80=A6=20(#134?= - =?UTF-8?q?9)?= -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -Upstream Patch Link: https://github.com/snowflakedb/gosnowflake/commit/40e4f5c874dcc003f6318a355b66da8056db5ce4.patch ---- - .../snowflakedb/gosnowflake/secure_storage_manager.go | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -index ab33874e..a823a52e 100644 ---- a/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -+++ b/vendor/github.com/snowflakedb/gosnowflake/secure_storage_manager.go -@@ -340,7 +340,7 @@ func (ssm *fileBasedSecureStorageManager) ensurePermissionsAndOwner(f *os.File, - return nil - } - if strconv.Itoa(int(ownerUID)) != currentUser.Uid { -- return errors.New("incorrect owner of " + ssm.credDirPath) -+ return errors.New("incorrect owner of " + f.Name()) - } - return nil - } --- -2.34.1 - - From 45c6ddc1d8336398a0d3d5b69da5e21f5f4a8574 Mon Sep 17 00:00:00 2001 From: Kevin Lockwood Date: Wed, 25 Jun 2025 16:46:39 -0700 -Subject: [PATCH 5/5] [Low] Fix CVE-2025-46327 +Subject: [PATCH] [Low] Fix CVE-2025-46327 All other patches in this file are supporting patches backported so that this patch can apply. diff --git a/SPECS/telegraf/telegraf.spec b/SPECS/telegraf/telegraf.spec index f22cd3d5502..ae48d2165a6 100644 --- a/SPECS/telegraf/telegraf.spec +++ b/SPECS/telegraf/telegraf.spec @@ -24,7 +24,8 @@ Patch9: CVE-2025-27144.patch Patch10: CVE-2025-30215.patch Patch11: CVE-2025-22872.patch Patch12: CVE-2025-29923.patch -Patch13: CVE-2025-46327.patch +Patch13: CVE-2025-46327-prereqs.patch +Patch14: CVE-2025-46327.patch BuildRequires: golang BuildRequires: systemd-devel