diff --git a/SPECS/telegraf/CVE-2025-29923.patch b/SPECS/telegraf/CVE-2025-29923.patch new file mode 100644 index 00000000000..03937ae48a9 --- /dev/null +++ b/SPECS/telegraf/CVE-2025-29923.patch @@ -0,0 +1,285 @@ +From d236865b0cfa1b752ea4b7da666b1fdcd0acebb6 Mon Sep 17 00:00:00 2001 +From: Nedyalko Dyakov +Date: Wed, 19 Mar 2025 19:02:36 +0200 +Subject: [PATCH] fix: handle network error on SETINFO (#3295) (CVE-2025-29923) + +* fix: handle network error on SETINFO + +This fix addresses potential out of order responses as described in `CVE-2025-29923` + +* fix: deprecate DisableIndentity and introduce DisableIdentity + +Both options will work before V10. In v10 DisableIndentity will be dropped. The preferred flag to use is `DisableIdentity`. + +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 | 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 +--- 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..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 { + 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,9 @@ func (opt *RingOptions) clientOptions() *Options { + TLSConfig: opt.TLSConfig, + Limiter: opt.Limiter, + ++ 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..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 { + + 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,9 @@ func (opt *FailoverOptions) clientOptions() *Options { + + TLSConfig: opt.TLSConfig, + ++ DisableIdentity: opt.DisableIdentity, + DisableIndentity: opt.DisableIndentity, ++ + IdentitySuffix: opt.IdentitySuffix, + } + } +@@ -154,7 +167,9 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options { + + TLSConfig: opt.TLSConfig, + ++ DisableIdentity: opt.DisableIdentity, + DisableIndentity: opt.DisableIndentity, ++ + IdentitySuffix: opt.IdentitySuffix, + } + } +@@ -194,8 +209,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/CVE-2025-46327-prereqs.patch b/SPECS/telegraf/CVE-2025-46327-prereqs.patch new file mode 100644 index 00000000000..5a9cdb8b13e --- /dev/null +++ b/SPECS/telegraf/CVE-2025-46327-prereqs.patch @@ -0,0 +1,1635 @@ +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 new file mode 100644 index 00000000000..1fe8359178f --- /dev/null +++ b/SPECS/telegraf/CVE-2025-46327.patch @@ -0,0 +1,214 @@ +From ba94a4800e23621eff558ef18ce4b96ec5489ff0 Mon Sep 17 00:00:00 2001 +From: Piotr Fus +Date: Mon, 28 Apr 2025 15:15:00 +0200 +Subject: [PATCH] SNOW-1155452 Fix race condition on perm checking for easy + logging (#1382) + +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 a2bce2cf342..2508d1f42f0 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: 11%{?dist} +Release: 12%{?dist} License: MIT Vendor: Microsoft Corporation Distribution: Azure Linux @@ -24,6 +24,9 @@ Patch9: CVE-2025-27144.patch Patch10: CVE-2025-30215.patch Patch11: CVE-2025-22872.patch Patch12: CVE-2025-47913.patch +Patch13: CVE-2025-29923.patch +Patch14: CVE-2025-46327-prereqs.patch +Patch15: CVE-2025-46327.patch BuildRequires: golang BuildRequires: systemd-devel @@ -88,6 +91,10 @@ fi %dir %{_sysconfdir}/%{name}/telegraf.d %changelog +* Thu Nov 27 2025 Jyoti kanase - 1.31.0-12 +- Patch CVE-2025-29923 +- Patch CVE-2025-46327 + * Tue Nov 18 2025 Azure Linux Security Servicing Account - 1.31.0-11 - Patch for CVE-2025-47913 @@ -103,7 +110,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