From 7157dad9794c62e01bb6cdc237e158fe99c0a9f6 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 13 Jan 2026 14:30:04 +0100 Subject: [PATCH 1/9] ske kubeconfig login: encrypt cache --- internal/pkg/auth/storage.go | 2 ++ internal/pkg/cache/cache.go | 56 ++++++++++++++++++++++++++++++-- internal/pkg/cache/cache_test.go | 32 ++++++++++++++---- 3 files changed, 80 insertions(+), 10 deletions(-) diff --git a/internal/pkg/auth/storage.go b/internal/pkg/auth/storage.go index 5e857f6a7..e97db8c99 100644 --- a/internal/pkg/auth/storage.go +++ b/internal/pkg/auth/storage.go @@ -37,6 +37,7 @@ const ( PRIVATE_KEY authFieldKey = "private_key" TOKEN_CUSTOM_ENDPOINT authFieldKey = "token_custom_endpoint" IDP_TOKEN_ENDPOINT authFieldKey = "idp_token_endpoint" //nolint:gosec // linter false positive + CACHE_ENCRYPTION_KEY authFieldKey = "cache_encryption_key" ) const ( @@ -59,6 +60,7 @@ var authFieldKeys = []authFieldKey{ TOKEN_CUSTOM_ENDPOINT, IDP_TOKEN_ENDPOINT, authFlowType, + CACHE_ENCRYPTION_KEY, } // All fields that are set when a user logs in diff --git a/internal/pkg/cache/cache.go b/internal/pkg/cache/cache.go index cf019ecb2..1b0b6d0d3 100644 --- a/internal/pkg/cache/cache.go +++ b/internal/pkg/cache/cache.go @@ -1,15 +1,22 @@ package cache import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" "errors" "fmt" "os" "path/filepath" "regexp" + + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" ) var ( - cacheFolderPath string + cacheFolderPath string + cacheEncryptionKey []byte identifierRegex = regexp.MustCompile("^[a-zA-Z0-9-]+$") ErrorInvalidCacheIdentifier = fmt.Errorf("invalid cache identifier") @@ -21,6 +28,25 @@ func Init() error { return fmt.Errorf("get user cache dir: %w", err) } cacheFolderPath = filepath.Join(cacheDir, "stackit") + + key, _ := auth.GetAuthField(auth.CACHE_ENCRYPTION_KEY) + cacheEncryptionKey = nil + if key != "" { + cacheEncryptionKey, _ = base64.StdEncoding.DecodeString(key) + // invalid key length + if len(cacheEncryptionKey) != 32 { + cacheEncryptionKey = nil + } + } + if len(cacheEncryptionKey) == 0 { + cacheEncryptionKey = make([]byte, 32) + _, err := rand.Read(cacheEncryptionKey) + if err != nil { + return fmt.Errorf("cache encryption key: %v", err) + } + key := base64.StdEncoding.EncodeToString(cacheEncryptionKey) + return auth.SetAuthField(auth.CACHE_ENCRYPTION_KEY, key) + } return nil } @@ -32,7 +58,21 @@ func GetObject(identifier string) ([]byte, error) { return nil, ErrorInvalidCacheIdentifier } - return os.ReadFile(filepath.Join(cacheFolderPath, identifier)) + data, err := os.ReadFile(filepath.Join(cacheFolderPath, identifier)) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(cacheEncryptionKey) + if err != nil { + return nil, err + } + aead, err := cipher.NewGCMWithRandomNonce(block) + if err != nil { + return nil, err + } + + return aead.Open(nil, nil, data, nil) } func PutObject(identifier string, data []byte) error { @@ -48,7 +88,17 @@ func PutObject(identifier string, data []byte) error { return err } - return os.WriteFile(filepath.Join(cacheFolderPath, identifier), data, 0o600) + block, err := aes.NewCipher(cacheEncryptionKey) + if err != nil { + return err + } + aead, err := cipher.NewGCMWithRandomNonce(block) + if err != nil { + return err + } + encrypted := aead.Seal(nil, nil, data, nil) + + return os.WriteFile(filepath.Join(cacheFolderPath, identifier), encrypted, 0o600) } func DeleteObject(identifier string) error { diff --git a/internal/pkg/cache/cache_test.go b/internal/pkg/cache/cache_test.go index cc68c6590..29f5251ef 100644 --- a/internal/pkg/cache/cache_test.go +++ b/internal/pkg/cache/cache_test.go @@ -6,10 +6,11 @@ import ( "path/filepath" "testing" + "github.com/google/go-cmp/cmp" "github.com/google/uuid" ) -func TestGetObject(t *testing.T) { +func TestGetObjectErrors(t *testing.T) { if err := Init(); err != nil { t.Fatalf("cache init failed: %s", err) } @@ -20,12 +21,6 @@ func TestGetObject(t *testing.T) { expectFile bool expectedErr error }{ - { - description: "identifier exists", - identifier: "test-cache-get-exists", - expectFile: true, - expectedErr: nil, - }, { description: "identifier does not exist", identifier: "test-cache-get-not-exists", @@ -205,3 +200,26 @@ func TestDeleteObject(t *testing.T) { }) } } + +func TestWriteAndRead(t *testing.T) { + if err := Init(); err != nil { + t.Fatalf("cache init failed: %s", err) + } + + id := "test-cycle-" + uuid.NewString() + data := []byte("test-data") + err := PutObject(id, data) + if err != nil { + t.Fatalf("putobject failed: %v", err) + } + + readData, err := GetObject(id) + if err != nil { + t.Fatalf("getobject failed: %v", err) + } + + diff := cmp.Diff(data, readData) + if diff != "" { + t.Fatalf("unexpected data diff: %v", diff) + } +} From 0b3de3959d0ffcdb2c6a0615031d223f7de143fe Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 13 Jan 2026 15:55:58 +0100 Subject: [PATCH 2/9] ske kubeconfig login: regularly rotate cache encryption key --- internal/pkg/auth/storage.go | 24 +++++++++++++----------- internal/pkg/cache/cache.go | 27 +++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/internal/pkg/auth/storage.go b/internal/pkg/auth/storage.go index e97db8c99..686a0f677 100644 --- a/internal/pkg/auth/storage.go +++ b/internal/pkg/auth/storage.go @@ -27,17 +27,18 @@ const ( ) const ( - SESSION_EXPIRES_AT_UNIX authFieldKey = "session_expires_at_unix" - ACCESS_TOKEN authFieldKey = "access_token" - REFRESH_TOKEN authFieldKey = "refresh_token" - SERVICE_ACCOUNT_TOKEN authFieldKey = "service_account_token" - SERVICE_ACCOUNT_EMAIL authFieldKey = "service_account_email" - USER_EMAIL authFieldKey = "user_email" - SERVICE_ACCOUNT_KEY authFieldKey = "service_account_key" - PRIVATE_KEY authFieldKey = "private_key" - TOKEN_CUSTOM_ENDPOINT authFieldKey = "token_custom_endpoint" - IDP_TOKEN_ENDPOINT authFieldKey = "idp_token_endpoint" //nolint:gosec // linter false positive - CACHE_ENCRYPTION_KEY authFieldKey = "cache_encryption_key" + SESSION_EXPIRES_AT_UNIX authFieldKey = "session_expires_at_unix" + ACCESS_TOKEN authFieldKey = "access_token" + REFRESH_TOKEN authFieldKey = "refresh_token" + SERVICE_ACCOUNT_TOKEN authFieldKey = "service_account_token" + SERVICE_ACCOUNT_EMAIL authFieldKey = "service_account_email" + USER_EMAIL authFieldKey = "user_email" + SERVICE_ACCOUNT_KEY authFieldKey = "service_account_key" + PRIVATE_KEY authFieldKey = "private_key" + TOKEN_CUSTOM_ENDPOINT authFieldKey = "token_custom_endpoint" + IDP_TOKEN_ENDPOINT authFieldKey = "idp_token_endpoint" //nolint:gosec // linter false positive + CACHE_ENCRYPTION_KEY authFieldKey = "cache_encryption_key" + CACHE_ENCRYPTION_KEY_AGE authFieldKey = "cache_encryption_key_age" ) const ( @@ -61,6 +62,7 @@ var authFieldKeys = []authFieldKey{ IDP_TOKEN_ENDPOINT, authFlowType, CACHE_ENCRYPTION_KEY, + CACHE_ENCRYPTION_KEY_AGE, } // All fields that are set when a user logs in diff --git a/internal/pkg/cache/cache.go b/internal/pkg/cache/cache.go index 1b0b6d0d3..220d94c87 100644 --- a/internal/pkg/cache/cache.go +++ b/internal/pkg/cache/cache.go @@ -10,6 +10,8 @@ import ( "os" "path/filepath" "regexp" + "strconv" + "time" "github.com/stackitcloud/stackit-cli/internal/pkg/auth" ) @@ -22,6 +24,10 @@ var ( ErrorInvalidCacheIdentifier = fmt.Errorf("invalid cache identifier") ) +const ( + cacheKeyMaxAge = 90 * 24 * time.Hour +) + func Init() error { cacheDir, err := os.UserCacheDir() if err != nil { @@ -29,9 +35,19 @@ func Init() error { } cacheFolderPath = filepath.Join(cacheDir, "stackit") + // Encryption keys should only be used a limited number of times for aes-gcm. + // Thus, refresh the key periodically. This will invalidate all cached entries. key, _ := auth.GetAuthField(auth.CACHE_ENCRYPTION_KEY) + age, _ := auth.GetAuthField(auth.CACHE_ENCRYPTION_KEY_AGE) cacheEncryptionKey = nil - if key != "" { + var keyAge time.Time + if age != "" { + ageSeconds, err := strconv.ParseInt(age, 10, 64) + if err == nil { + keyAge = time.Unix(ageSeconds, 0) + } + } + if key != "" && keyAge.Add(cacheKeyMaxAge).After(time.Now()) { cacheEncryptionKey, _ = base64.StdEncoding.DecodeString(key) // invalid key length if len(cacheEncryptionKey) != 32 { @@ -45,7 +61,14 @@ func Init() error { return fmt.Errorf("cache encryption key: %v", err) } key := base64.StdEncoding.EncodeToString(cacheEncryptionKey) - return auth.SetAuthField(auth.CACHE_ENCRYPTION_KEY, key) + err = auth.SetAuthField(auth.CACHE_ENCRYPTION_KEY, key) + if err != nil { + return fmt.Errorf("save cache encryption key: %v", err) + } + err = auth.SetAuthField(auth.CACHE_ENCRYPTION_KEY_AGE, fmt.Sprint(time.Now().Unix())) + if err != nil { + return fmt.Errorf("save cache encryption key age: %v", err) + } } return nil } From 079177597772d6b8397cfcff912c09dc3d13c569 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 13 Jan 2026 16:11:04 +0100 Subject: [PATCH 3/9] make linter happy --- internal/pkg/cache/cache.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/pkg/cache/cache.go b/internal/pkg/cache/cache.go index 220d94c87..4a9b4ce9f 100644 --- a/internal/pkg/cache/cache.go +++ b/internal/pkg/cache/cache.go @@ -58,16 +58,16 @@ func Init() error { cacheEncryptionKey = make([]byte, 32) _, err := rand.Read(cacheEncryptionKey) if err != nil { - return fmt.Errorf("cache encryption key: %v", err) + return fmt.Errorf("cache encryption key: %w", err) } key := base64.StdEncoding.EncodeToString(cacheEncryptionKey) err = auth.SetAuthField(auth.CACHE_ENCRYPTION_KEY, key) if err != nil { - return fmt.Errorf("save cache encryption key: %v", err) + return fmt.Errorf("save cache encryption key: %w", err) } err = auth.SetAuthField(auth.CACHE_ENCRYPTION_KEY_AGE, fmt.Sprint(time.Now().Unix())) if err != nil { - return fmt.Errorf("save cache encryption key age: %v", err) + return fmt.Errorf("save cache encryption key age: %w", err) } } return nil From 23e9c3b5d4b76aa992a426a3e8c3dfc07d86ea15 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 16 Jan 2026 13:31:30 +0100 Subject: [PATCH 4/9] implement cache cleanup --- internal/pkg/cache/cache.go | 26 ++++++++++++ internal/pkg/cache/cache_test.go | 68 +++++++++++++++++++++++++++++--- 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/internal/pkg/cache/cache.go b/internal/pkg/cache/cache.go index 4a9b4ce9f..637bdc19a 100644 --- a/internal/pkg/cache/cache.go +++ b/internal/pkg/cache/cache.go @@ -69,6 +69,9 @@ func Init() error { if err != nil { return fmt.Errorf("save cache encryption key age: %w", err) } + if err := cleanupCache(); err != nil { + return err + } } return nil } @@ -144,3 +147,26 @@ func validateCacheFolderPath() error { } return nil } + +func cleanupCache() error { + if err := validateCacheFolderPath(); err != nil { + return err + } + + entries, err := os.ReadDir(cacheFolderPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + + for _, entry := range entries { + name := entry.Name() + err := DeleteObject(name) + if err != nil && err != ErrorInvalidCacheIdentifier { + return err + } + } + return nil +} diff --git a/internal/pkg/cache/cache_test.go b/internal/pkg/cache/cache_test.go index 29f5251ef..7cfd0ca1b 100644 --- a/internal/pkg/cache/cache_test.go +++ b/internal/pkg/cache/cache_test.go @@ -8,6 +8,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/uuid" + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" ) func TestGetObjectErrors(t *testing.T) { @@ -201,7 +202,60 @@ func TestDeleteObject(t *testing.T) { } } +func clearKeys(t *testing.T) { + t.Helper() + err := auth.DeleteAuthField(auth.CACHE_ENCRYPTION_KEY) + if err != nil { + t.Fatalf("delete cache encryption key: %v", err) + } + err = auth.DeleteAuthField(auth.CACHE_ENCRYPTION_KEY_AGE) + if err != nil { + t.Fatalf("delete cache encryption key age: %v", err) + } +} + func TestWriteAndRead(t *testing.T) { + for _, tt := range []struct { + name string + clearKeys bool + }{ + { + name: "normal", + }, + { + name: "fresh keys", + clearKeys: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + if tt.clearKeys { + clearKeys(t) + } + if err := Init(); err != nil { + t.Fatalf("cache init failed: %s", err) + } + + id := "test-cycle-" + uuid.NewString() + data := []byte("test-data") + err := PutObject(id, data) + if err != nil { + t.Fatalf("putobject failed: %v", err) + } + + readData, err := GetObject(id) + if err != nil { + t.Fatalf("getobject failed: %v", err) + } + + diff := cmp.Diff(data, readData) + if diff != "" { + t.Fatalf("unexpected data diff: %v", diff) + } + }) + } +} + +func TestCacheCleanup(t *testing.T) { if err := Init(); err != nil { t.Fatalf("cache init failed: %s", err) } @@ -213,13 +267,15 @@ func TestWriteAndRead(t *testing.T) { t.Fatalf("putobject failed: %v", err) } - readData, err := GetObject(id) - if err != nil { - t.Fatalf("getobject failed: %v", err) + clearKeys(t) + + // initialize again to trigger cache cleanup + if err := Init(); err != nil { + t.Fatalf("cache init failed: %s", err) } - diff := cmp.Diff(data, readData) - if diff != "" { - t.Fatalf("unexpected data diff: %v", diff) + _, err = GetObject(id) + if !errors.Is(err, os.ErrNotExist) { + t.Fatalf("getobject failed with unexpected error: %v", err) } } From aafb00d2980c3d9701a9f1592e5e3e1691b0404f Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 16 Jan 2026 13:32:16 +0100 Subject: [PATCH 5/9] add comment --- internal/pkg/cache/cache.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/pkg/cache/cache.go b/internal/pkg/cache/cache.go index 637bdc19a..c30206b03 100644 --- a/internal/pkg/cache/cache.go +++ b/internal/pkg/cache/cache.go @@ -69,6 +69,7 @@ func Init() error { if err != nil { return fmt.Errorf("save cache encryption key age: %w", err) } + // cleanup old cache entries as they won't be readable anymore if err := cleanupCache(); err != nil { return err } From 7f7284c51fa406a521745b77608a8d5c36371ba5 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 16 Jan 2026 16:35:47 +0100 Subject: [PATCH 6/9] fix linter error --- internal/pkg/cache/cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/pkg/cache/cache.go b/internal/pkg/cache/cache.go index c30206b03..9eb293767 100644 --- a/internal/pkg/cache/cache.go +++ b/internal/pkg/cache/cache.go @@ -165,7 +165,7 @@ func cleanupCache() error { for _, entry := range entries { name := entry.Name() err := DeleteObject(name) - if err != nil && err != ErrorInvalidCacheIdentifier { + if err != nil && !errors.Is(err, ErrorInvalidCacheIdentifier) { return err } } From a4438b3cc675dea5b58b297010e3b92a1a7809e4 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 16 Jan 2026 16:36:12 +0100 Subject: [PATCH 7/9] overwrite cache directory during tests --- internal/pkg/cache/cache.go | 14 +++++++++++--- internal/pkg/cache/cache_test.go | 28 +++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/internal/pkg/cache/cache.go b/internal/pkg/cache/cache.go index 9eb293767..beaf87d12 100644 --- a/internal/pkg/cache/cache.go +++ b/internal/pkg/cache/cache.go @@ -17,6 +17,7 @@ import ( ) var ( + cacheDirOverwrite string // for testing only cacheFolderPath string cacheEncryptionKey []byte @@ -29,10 +30,17 @@ const ( ) func Init() error { - cacheDir, err := os.UserCacheDir() - if err != nil { - return fmt.Errorf("get user cache dir: %w", err) + var cacheDir string + if cacheDirOverwrite == "" { + var err error + cacheDir, err = os.UserCacheDir() + if err != nil { + return fmt.Errorf("get user cache dir: %w", err) + } + } else { + cacheDir = cacheDirOverwrite } + cacheFolderPath = filepath.Join(cacheDir, "stackit") // Encryption keys should only be used a limited number of times for aes-gcm. diff --git a/internal/pkg/cache/cache_test.go b/internal/pkg/cache/cache_test.go index 7cfd0ca1b..df40cde90 100644 --- a/internal/pkg/cache/cache_test.go +++ b/internal/pkg/cache/cache_test.go @@ -11,7 +11,15 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/auth" ) +func overwriteCacheDir(t *testing.T) func() { + cacheDirOverwrite = t.TempDir() + return func() { + cacheDirOverwrite = "" + } +} + func TestGetObjectErrors(t *testing.T) { + defer overwriteCacheDir(t)() if err := Init(); err != nil { t.Fatalf("cache init failed: %s", err) } @@ -42,10 +50,6 @@ func TestGetObjectErrors(t *testing.T) { // setup if tt.expectFile { - err := os.MkdirAll(cacheFolderPath, 0o750) - if err != nil { - t.Fatalf("create cache folder: %s", err.Error()) - } path := filepath.Join(cacheFolderPath, id) if err := os.WriteFile(path, []byte("dummy"), 0o600); err != nil { t.Fatalf("setup: WriteFile (%s) failed", path) @@ -71,6 +75,7 @@ func TestGetObjectErrors(t *testing.T) { } } func TestPutObject(t *testing.T) { + defer overwriteCacheDir(t)() if err := Init(); err != nil { t.Fatalf("cache init failed: %s", err) } @@ -145,6 +150,7 @@ func TestPutObject(t *testing.T) { } func TestDeleteObject(t *testing.T) { + defer overwriteCacheDir(t)() if err := Init(); err != nil { t.Fatalf("cache init failed: %s", err) } @@ -182,8 +188,11 @@ func TestDeleteObject(t *testing.T) { // setup if tt.existingFile { + if err := os.MkdirAll(cacheFolderPath, 0o700); err != nil { + t.Fatalf("setup: MkdirAll (%s) failed: %v", path, err) + } if err := os.WriteFile(path, []byte("dummy"), 0o600); err != nil { - t.Fatalf("setup: WriteFile (%s) failed", path) + t.Fatalf("setup: WriteFile (%s) failed: %v", path, err) } } // test @@ -228,6 +237,7 @@ func TestWriteAndRead(t *testing.T) { }, } { t.Run(tt.name, func(t *testing.T) { + defer overwriteCacheDir(t)() if tt.clearKeys { clearKeys(t) } @@ -256,6 +266,7 @@ func TestWriteAndRead(t *testing.T) { } func TestCacheCleanup(t *testing.T) { + defer overwriteCacheDir(t)() if err := Init(); err != nil { t.Fatalf("cache init failed: %s", err) } @@ -279,3 +290,10 @@ func TestCacheCleanup(t *testing.T) { t.Fatalf("getobject failed with unexpected error: %v", err) } } + +func TestInit(t *testing.T) { + // test that init without cache directory overwrite works + if err := Init(); err != nil { + t.Fatalf("cache init failed: %s", err) + } +} From 77b8b85c3a415eb8ff3537ba6bb17859ebc1d22a Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 19 Jan 2026 08:54:23 +0100 Subject: [PATCH 8/9] remove dead test code --- internal/pkg/cache/cache_test.go | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/internal/pkg/cache/cache_test.go b/internal/pkg/cache/cache_test.go index df40cde90..84092e209 100644 --- a/internal/pkg/cache/cache_test.go +++ b/internal/pkg/cache/cache_test.go @@ -27,19 +27,16 @@ func TestGetObjectErrors(t *testing.T) { tests := []struct { description string identifier string - expectFile bool expectedErr error }{ { description: "identifier does not exist", identifier: "test-cache-get-not-exists", - expectFile: false, expectedErr: os.ErrNotExist, }, { description: "identifier is invalid", identifier: "in../../valid", - expectFile: false, expectedErr: ErrorInvalidCacheIdentifier, }, } @@ -48,13 +45,6 @@ func TestGetObjectErrors(t *testing.T) { t.Run(tt.description, func(t *testing.T) { id := tt.identifier + "-" + uuid.NewString() - // setup - if tt.expectFile { - path := filepath.Join(cacheFolderPath, id) - if err := os.WriteFile(path, []byte("dummy"), 0o600); err != nil { - t.Fatalf("setup: WriteFile (%s) failed", path) - } - } // test file, err := GetObject(id) @@ -62,14 +52,8 @@ func TestGetObjectErrors(t *testing.T) { t.Fatalf("returned error (%q) does not match %q", err.Error(), tt.expectedErr.Error()) } - if tt.expectFile { - if len(file) < 1 { - t.Fatalf("expected a file but byte array is empty (len %d)", len(file)) - } - } else { - if len(file) > 0 { - t.Fatalf("didn't expect a file, but byte array is not empty (len %d)", len(file)) - } + if len(file) > 0 { + t.Fatalf("didn't expect a file, but byte array is not empty (len %d)", len(file)) } }) } From 579b575a16a8db548d3a90485bd483cbe617bd12 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Mon, 19 Jan 2026 08:54:32 +0100 Subject: [PATCH 9/9] fix failing test --- internal/pkg/cache/cache_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/pkg/cache/cache_test.go b/internal/pkg/cache/cache_test.go index 84092e209..4ef45891b 100644 --- a/internal/pkg/cache/cache_test.go +++ b/internal/pkg/cache/cache_test.go @@ -113,6 +113,10 @@ func TestPutObject(t *testing.T) { // setup if tt.existingFile { + err := os.MkdirAll(cacheFolderPath, 0o750) + if err != nil { + t.Fatalf("create cache folder: %s", err.Error()) + } if err := os.WriteFile(path, []byte("dummy"), 0o600); err != nil { t.Fatalf("setup: WriteFile (%s) failed", path) }