Skip to content

Commit c97f9fe

Browse files
authored
Merge branch 'main' into feature/onboard-logs-service
2 parents 0e110af + 9069021 commit c97f9fe

File tree

3 files changed

+238
-46
lines changed

3 files changed

+238
-46
lines changed

internal/pkg/auth/storage.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,18 @@ const (
2727
)
2828

2929
const (
30-
SESSION_EXPIRES_AT_UNIX authFieldKey = "session_expires_at_unix"
31-
ACCESS_TOKEN authFieldKey = "access_token"
32-
REFRESH_TOKEN authFieldKey = "refresh_token"
33-
SERVICE_ACCOUNT_TOKEN authFieldKey = "service_account_token"
34-
SERVICE_ACCOUNT_EMAIL authFieldKey = "service_account_email"
35-
USER_EMAIL authFieldKey = "user_email"
36-
SERVICE_ACCOUNT_KEY authFieldKey = "service_account_key"
37-
PRIVATE_KEY authFieldKey = "private_key"
38-
TOKEN_CUSTOM_ENDPOINT authFieldKey = "token_custom_endpoint"
39-
IDP_TOKEN_ENDPOINT authFieldKey = "idp_token_endpoint" //nolint:gosec // linter false positive
30+
SESSION_EXPIRES_AT_UNIX authFieldKey = "session_expires_at_unix"
31+
ACCESS_TOKEN authFieldKey = "access_token"
32+
REFRESH_TOKEN authFieldKey = "refresh_token"
33+
SERVICE_ACCOUNT_TOKEN authFieldKey = "service_account_token"
34+
SERVICE_ACCOUNT_EMAIL authFieldKey = "service_account_email"
35+
USER_EMAIL authFieldKey = "user_email"
36+
SERVICE_ACCOUNT_KEY authFieldKey = "service_account_key"
37+
PRIVATE_KEY authFieldKey = "private_key"
38+
TOKEN_CUSTOM_ENDPOINT authFieldKey = "token_custom_endpoint"
39+
IDP_TOKEN_ENDPOINT authFieldKey = "idp_token_endpoint" //nolint:gosec // linter false positive
40+
CACHE_ENCRYPTION_KEY authFieldKey = "cache_encryption_key"
41+
CACHE_ENCRYPTION_KEY_AGE authFieldKey = "cache_encryption_key_age"
4042
)
4143

4244
const (
@@ -59,6 +61,8 @@ var authFieldKeys = []authFieldKey{
5961
TOKEN_CUSTOM_ENDPOINT,
6062
IDP_TOKEN_ENDPOINT,
6163
authFlowType,
64+
CACHE_ENCRYPTION_KEY,
65+
CACHE_ENCRYPTION_KEY_AGE,
6266
}
6367

6468
// All fields that are set when a user logs in

internal/pkg/cache/cache.go

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,87 @@
11
package cache
22

33
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
"crypto/rand"
7+
"encoding/base64"
48
"errors"
59
"fmt"
610
"os"
711
"path/filepath"
812
"regexp"
13+
"strconv"
14+
"time"
15+
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/auth"
917
)
1018

1119
var (
12-
cacheFolderPath string
20+
cacheDirOverwrite string // for testing only
21+
cacheFolderPath string
22+
cacheEncryptionKey []byte
1323

1424
identifierRegex = regexp.MustCompile("^[a-zA-Z0-9-]+$")
1525
ErrorInvalidCacheIdentifier = fmt.Errorf("invalid cache identifier")
1626
)
1727

28+
const (
29+
cacheKeyMaxAge = 90 * 24 * time.Hour
30+
)
31+
1832
func Init() error {
19-
cacheDir, err := os.UserCacheDir()
20-
if err != nil {
21-
return fmt.Errorf("get user cache dir: %w", err)
33+
var cacheDir string
34+
if cacheDirOverwrite == "" {
35+
var err error
36+
cacheDir, err = os.UserCacheDir()
37+
if err != nil {
38+
return fmt.Errorf("get user cache dir: %w", err)
39+
}
40+
} else {
41+
cacheDir = cacheDirOverwrite
2242
}
43+
2344
cacheFolderPath = filepath.Join(cacheDir, "stackit")
45+
46+
// Encryption keys should only be used a limited number of times for aes-gcm.
47+
// Thus, refresh the key periodically. This will invalidate all cached entries.
48+
key, _ := auth.GetAuthField(auth.CACHE_ENCRYPTION_KEY)
49+
age, _ := auth.GetAuthField(auth.CACHE_ENCRYPTION_KEY_AGE)
50+
cacheEncryptionKey = nil
51+
var keyAge time.Time
52+
if age != "" {
53+
ageSeconds, err := strconv.ParseInt(age, 10, 64)
54+
if err == nil {
55+
keyAge = time.Unix(ageSeconds, 0)
56+
}
57+
}
58+
if key != "" && keyAge.Add(cacheKeyMaxAge).After(time.Now()) {
59+
cacheEncryptionKey, _ = base64.StdEncoding.DecodeString(key)
60+
// invalid key length
61+
if len(cacheEncryptionKey) != 32 {
62+
cacheEncryptionKey = nil
63+
}
64+
}
65+
if len(cacheEncryptionKey) == 0 {
66+
cacheEncryptionKey = make([]byte, 32)
67+
_, err := rand.Read(cacheEncryptionKey)
68+
if err != nil {
69+
return fmt.Errorf("cache encryption key: %w", err)
70+
}
71+
key := base64.StdEncoding.EncodeToString(cacheEncryptionKey)
72+
err = auth.SetAuthField(auth.CACHE_ENCRYPTION_KEY, key)
73+
if err != nil {
74+
return fmt.Errorf("save cache encryption key: %w", err)
75+
}
76+
err = auth.SetAuthField(auth.CACHE_ENCRYPTION_KEY_AGE, fmt.Sprint(time.Now().Unix()))
77+
if err != nil {
78+
return fmt.Errorf("save cache encryption key age: %w", err)
79+
}
80+
// cleanup old cache entries as they won't be readable anymore
81+
if err := cleanupCache(); err != nil {
82+
return err
83+
}
84+
}
2485
return nil
2586
}
2687

@@ -32,7 +93,21 @@ func GetObject(identifier string) ([]byte, error) {
3293
return nil, ErrorInvalidCacheIdentifier
3394
}
3495

35-
return os.ReadFile(filepath.Join(cacheFolderPath, identifier))
96+
data, err := os.ReadFile(filepath.Join(cacheFolderPath, identifier))
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
block, err := aes.NewCipher(cacheEncryptionKey)
102+
if err != nil {
103+
return nil, err
104+
}
105+
aead, err := cipher.NewGCMWithRandomNonce(block)
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
return aead.Open(nil, nil, data, nil)
36111
}
37112

38113
func PutObject(identifier string, data []byte) error {
@@ -48,7 +123,17 @@ func PutObject(identifier string, data []byte) error {
48123
return err
49124
}
50125

51-
return os.WriteFile(filepath.Join(cacheFolderPath, identifier), data, 0o600)
126+
block, err := aes.NewCipher(cacheEncryptionKey)
127+
if err != nil {
128+
return err
129+
}
130+
aead, err := cipher.NewGCMWithRandomNonce(block)
131+
if err != nil {
132+
return err
133+
}
134+
encrypted := aead.Seal(nil, nil, data, nil)
135+
136+
return os.WriteFile(filepath.Join(cacheFolderPath, identifier), encrypted, 0o600)
52137
}
53138

54139
func DeleteObject(identifier string) error {
@@ -71,3 +156,26 @@ func validateCacheFolderPath() error {
71156
}
72157
return nil
73158
}
159+
160+
func cleanupCache() error {
161+
if err := validateCacheFolderPath(); err != nil {
162+
return err
163+
}
164+
165+
entries, err := os.ReadDir(cacheFolderPath)
166+
if err != nil {
167+
if errors.Is(err, os.ErrNotExist) {
168+
return nil
169+
}
170+
return err
171+
}
172+
173+
for _, entry := range entries {
174+
name := entry.Name()
175+
err := DeleteObject(name)
176+
if err != nil && !errors.Is(err, ErrorInvalidCacheIdentifier) {
177+
return err
178+
}
179+
}
180+
return nil
181+
}

0 commit comments

Comments
 (0)