frostfs-node/pkg/services/session/storage/persistent/storage.go

154 lines
3.5 KiB
Go

package persistent
import (
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/session/storage"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"go.etcd.io/bbolt"
"go.uber.org/zap"
)
// TokenStore is a wrapper around persistent K:V db that
// allows creating (storing), retrieving and expiring
// (removing) session tokens.
type TokenStore struct {
db *bbolt.DB
l *logger.Logger
// optional AES-256 algorithm
// encryption in Galois/Counter
// Mode
gcm cipher.AEAD
}
var sessionsBucket = []byte("sessions")
// NewTokenStore creates, initializes and returns a new TokenStore instance.
//
// The elements of the instance are stored in bolt DB.
func NewTokenStore(path string, opts ...Option) (*TokenStore, error) {
cfg := defaultCfg()
for _, o := range opts {
o(cfg)
}
db, err := bbolt.Open(path, 0o600,
&bbolt.Options{
Timeout: cfg.timeout,
})
if err != nil {
return nil, fmt.Errorf("can't open bbolt at %s: %w", path, err)
}
err = db.Update(func(tx *bbolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(sessionsBucket)
return err
})
if err != nil {
_ = db.Close()
return nil, fmt.Errorf("could not init session bucket: %w", err)
}
ts := &TokenStore{db: db, l: cfg.l}
// enable encryption if it
// was configured so
if cfg.privateKey != nil {
rawKey := make([]byte, (cfg.privateKey.Curve.Params().N.BitLen()+7)/8)
cfg.privateKey.D.FillBytes(rawKey)
c, err := aes.NewCipher(rawKey)
if err != nil {
return nil, fmt.Errorf("could not create cipher block: %w", err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, fmt.Errorf("could not wrapp cipher block in Galois Counter Mode: %w", err)
}
ts.gcm = gcm
}
return ts, nil
}
// Get returns private token corresponding to the given identifiers.
//
// Returns nil is there is no element in storage.
func (s *TokenStore) Get(ownerID user.ID, tokenID []byte) (t *storage.PrivateToken) {
err := s.db.View(func(tx *bbolt.Tx) error {
rootBucket := tx.Bucket(sessionsBucket)
ownerBucket := rootBucket.Bucket(ownerID.WalletBytes())
if ownerBucket == nil {
return nil
}
rawToken := ownerBucket.Get(tokenID)
if rawToken == nil {
return nil
}
var err error
t, err = s.unpackToken(rawToken)
return err
})
if err != nil {
s.l.Error(logs.PersistentCouldNotGetSessionFromPersistentStorage,
zap.Error(err),
zap.Stringer("ownerID", ownerID),
zap.String("tokenID", hex.EncodeToString(tokenID)),
)
}
return
}
// RemoveOld removes all tokens expired since provided epoch.
func (s *TokenStore) RemoveOld(epoch uint64) {
err := s.db.Update(func(tx *bbolt.Tx) error {
rootBucket := tx.Bucket(sessionsBucket)
// iterating over ownerIDs
return iterateNestedBuckets(rootBucket, func(b *bbolt.Bucket) error {
c := b.Cursor()
var err error
// iterating over fixed ownerID's tokens
for k, v := c.First(); k != nil; k, v = c.Next() {
if epochFromToken(v) <= epoch {
err = c.Delete()
if err != nil {
s.l.Error(logs.PersistentCouldNotDeleteSToken,
zap.String("token_id", hex.EncodeToString(k)),
)
}
}
}
return nil
})
})
if err != nil {
s.l.Error(logs.PersistentCouldNotCleanUpExpiredTokens,
zap.Uint64("epoch", epoch),
)
}
}
// Close closes database connection.
func (s *TokenStore) Close() error {
return s.db.Close()
}