forked from TrueCloudLab/frostfs-api-go
session: support the expiration of private tokens
All sessions in NeoFS has limited in epochs lifetime. There is a need to limit the lifetime of private session tokens. This commmit: * extends PrivateToken interface with Expired method; * defines EpochLifetimeStore interface with RemoveExpired method and embeds it to PrivateTokenStore interface; * adds epoch value parameter to private token constructor.
This commit is contained in:
parent
8cbdb9183f
commit
4fa7360cd1
5 changed files with 116 additions and 4 deletions
|
@ -11,12 +11,14 @@ import (
|
||||||
type pToken struct {
|
type pToken struct {
|
||||||
// private session token
|
// private session token
|
||||||
sessionKey *ecdsa.PrivateKey
|
sessionKey *ecdsa.PrivateKey
|
||||||
|
// last epoch of the lifetime
|
||||||
|
validUntil uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPrivateToken creates PrivateToken instance.
|
// NewPrivateToken creates PrivateToken instance that expires after passed epoch.
|
||||||
//
|
//
|
||||||
// Returns non-nil error on key generation error.
|
// Returns non-nil error on key generation error.
|
||||||
func NewPrivateToken() (PrivateToken, error) {
|
func NewPrivateToken(validUntil uint64) (PrivateToken, error) {
|
||||||
sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -24,6 +26,7 @@ func NewPrivateToken() (PrivateToken, error) {
|
||||||
|
|
||||||
return &pToken{
|
return &pToken{
|
||||||
sessionKey: sk,
|
sessionKey: sk,
|
||||||
|
validUntil: validUntil,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,3 +39,7 @@ func (t *pToken) Sign(data []byte) ([]byte, error) {
|
||||||
func (t *pToken) PublicKey() []byte {
|
func (t *pToken) PublicKey() []byte {
|
||||||
return crypto.MarshalPublicKey(&t.sessionKey.PublicKey)
|
return crypto.MarshalPublicKey(&t.sessionKey.PublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *pToken) Expired(epoch uint64) bool {
|
||||||
|
return t.validUntil < epoch
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
func TestPrivateToken(t *testing.T) {
|
func TestPrivateToken(t *testing.T) {
|
||||||
// create new private token
|
// create new private token
|
||||||
pToken, err := NewPrivateToken()
|
pToken, err := NewPrivateToken(0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// generate data to sign
|
// generate data to sign
|
||||||
|
@ -31,3 +31,20 @@ func TestPrivateToken(t *testing.T) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPToken_Expired(t *testing.T) {
|
||||||
|
e := uint64(10)
|
||||||
|
|
||||||
|
var token PrivateToken = &pToken{
|
||||||
|
validUntil: e,
|
||||||
|
}
|
||||||
|
|
||||||
|
// must not be expired in the epoch before last
|
||||||
|
require.False(t, token.Expired(e-1))
|
||||||
|
|
||||||
|
// must not be expired in the last epoch
|
||||||
|
require.False(t, token.Expired(e))
|
||||||
|
|
||||||
|
// must be expired in the epoch after last
|
||||||
|
require.True(t, token.Expired(e+1))
|
||||||
|
}
|
||||||
|
|
|
@ -45,3 +45,20 @@ func (s *mapTokenStore) Fetch(id TokenID) (PrivateToken, error) {
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveExpired removes all the map elements that are expired in the passed epoch.
|
||||||
|
//
|
||||||
|
// Resulting error is always nil.
|
||||||
|
func (s *mapTokenStore) RemoveExpired(epoch uint64) error {
|
||||||
|
s.Lock()
|
||||||
|
|
||||||
|
for key, token := range s.tokens {
|
||||||
|
if token.Expired(epoch) {
|
||||||
|
delete(s.tokens, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
func TestMapTokenStore(t *testing.T) {
|
func TestMapTokenStore(t *testing.T) {
|
||||||
// create new private token
|
// create new private token
|
||||||
pToken, err := NewPrivateToken()
|
pToken, err := NewPrivateToken(0)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// create map token store
|
// create map token store
|
||||||
|
@ -33,3 +33,64 @@ func TestMapTokenStore(t *testing.T) {
|
||||||
// ascertain that returned token equals to initial
|
// ascertain that returned token equals to initial
|
||||||
require.Equal(t, pToken, res)
|
require.Equal(t, pToken, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMapTokenStore_RemoveExpired(t *testing.T) {
|
||||||
|
// create some epoch number
|
||||||
|
e1 := uint64(1)
|
||||||
|
|
||||||
|
// create private token that expires after e1
|
||||||
|
tok1, err := NewPrivateToken(e1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create some greater than e1 epoch number
|
||||||
|
e2 := e1 + 1
|
||||||
|
|
||||||
|
// create private token that expires after e2
|
||||||
|
tok2, err := NewPrivateToken(e2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create token store instance
|
||||||
|
s := NewMapTokenStore()
|
||||||
|
|
||||||
|
// create storage keys for tokens
|
||||||
|
id1, err := refs.NewUUID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
id2, err := refs.NewUUID()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assertPresence := func(ids ...TokenID) {
|
||||||
|
for i := range ids {
|
||||||
|
_, err = s.Fetch(ids[i])
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertAbsence := func(ids ...TokenID) {
|
||||||
|
for i := range ids {
|
||||||
|
_, err = s.Fetch(ids[i])
|
||||||
|
require.EqualError(t, err, ErrPrivateTokenNotFound.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store both tokens
|
||||||
|
require.NoError(t, s.Store(id1, tok1))
|
||||||
|
require.NoError(t, s.Store(id2, tok2))
|
||||||
|
|
||||||
|
// ascertain that both tokens are available
|
||||||
|
assertPresence(id1, id2)
|
||||||
|
|
||||||
|
// perform cleaning for epoch in which both tokens are not expired
|
||||||
|
require.NoError(t, s.RemoveExpired(e1))
|
||||||
|
|
||||||
|
// ascertain that both tokens are still available
|
||||||
|
assertPresence(id1, id2)
|
||||||
|
|
||||||
|
// perform cleaning for epoch greater than e1 and not greater than e2
|
||||||
|
require.NoError(t, s.RemoveExpired(e1+1))
|
||||||
|
|
||||||
|
// ascertain that tok1 was removed
|
||||||
|
assertAbsence(id1)
|
||||||
|
|
||||||
|
// ascertain that tok2 was not removed
|
||||||
|
assertPresence(id2)
|
||||||
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@ type PrivateToken interface {
|
||||||
// Resulting signature must be verified by crypto.Verify function
|
// Resulting signature must be verified by crypto.Verify function
|
||||||
// with the session public key.
|
// with the session public key.
|
||||||
Sign([]byte) ([]byte, error)
|
Sign([]byte) ([]byte, error)
|
||||||
|
|
||||||
|
// Expired must return true if and only if private token is expired in the given epoch number.
|
||||||
|
Expired(uint64) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivateTokenSource is an interface of private token storage with read access.
|
// PrivateTokenSource is an interface of private token storage with read access.
|
||||||
|
@ -27,9 +30,16 @@ type PrivateTokenSource interface {
|
||||||
Fetch(TokenID) (PrivateToken, error)
|
Fetch(TokenID) (PrivateToken, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EpochLifetimeStore is an interface of the storage of elements that lifetime is limited by NeoFS epoch.
|
||||||
|
type EpochLifetimeStore interface {
|
||||||
|
// RemoveExpired must remove all elements that are expired in the given epoch.
|
||||||
|
RemoveExpired(uint64) error
|
||||||
|
}
|
||||||
|
|
||||||
// PrivateTokenStore is an interface of the storage of private tokens addressable by TokenID.
|
// PrivateTokenStore is an interface of the storage of private tokens addressable by TokenID.
|
||||||
type PrivateTokenStore interface {
|
type PrivateTokenStore interface {
|
||||||
PrivateTokenSource
|
PrivateTokenSource
|
||||||
|
EpochLifetimeStore
|
||||||
|
|
||||||
// Store must save passed private token in the storage under the given key.
|
// Store must save passed private token in the storage under the given key.
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue